1 /**
2 Package containings meta utilities line swizzling or mixins.
3 */
4 module sily.meta.swizzle;
5 import std.array;
6 import std.string;
7 import std.algorithm;
8 
9 // for my sanity or for sanity of anyone who's seeing this
10 // is(typeof(A + B)) is basically a check if two types
11 // are compatible together.
12 // so 0 + "A" will return false, but 0 + true will return true
13 /**
14 Returns true if op between type T and type R is compatible and returns type convertable to E (T)
15 Example:
16 ---
17 // Assuming "this" is a vector with type "T"
18 auto opBinary(string op, R)(in Vector!(R, N) b) const 
19 if ( isValidOp(op, T, R) ) {
20     // op
21 }
22 // If we want for return value to be float
23 auto opBinary(string op, R)(in Vector!(R, N) b) const 
24 if ( isValidOp(op, T, R, float) ) {
25     // op
26 }
27 ---
28 */
29 bool isValidOp(string op, T, R, E = T)() pure {
30     mixin(`return is( typeof( mixin("T.init"` ~ op ~ `"R.init" ) ): E );`);
31 }
32 
33 /* --------------------------------- Swizzle -------------------------------- */
34 
35 /**
36 Allows swizzling for vector types "Vector!(Type, Size)". Creates opDispatch
37 containing available swizzle
38 Usage:
39 ---
40 // Params: T, size_t size,  string dataArrayName, string accessString, string dataSep, string accessSep
41 
42 // Setting up separators automatically
43 mixin accessByString!(T, 4, "data", "x y z w");
44 
45 // Data separator can be anything
46 mixin accessByString!(int, 3, "data", "x,y,z", ",");
47 
48 // Same for access separator
49 mixin accessByString!(float, 3, "data", "x,y,z/r,g,b", ",", "/");
50 
51 // What sily.vector uses:
52 static if (N == 2 || N == 3 || N == 4) {
53     static if (N == 2) private enum AS = "x y|w h|u v"; 
54     else
55     static if (N == 3) private enum AS = "x y z|w h d|u v t|r g b"; 
56     else
57     static if (N == 4) private enum AS = "x y z w|r g b a";
58     mixin accessByString!(T, N, "data", AS);
59 }
60 ---
61 */
62 mixin template accessByString( T, size_t N, string data, string accessString, string dataSep=" ", string accessSep="|")
63     if( isCompatibleArrayAccessStrings(N,accessString,dataSep,accessSep) ) {
64     pure @property {
65         T opDispatch(string v)() const if( getIndex(accessString,v,dataSep,accessSep) != -1 ) { 
66             mixin( format( "return this.%s[%d];", data, getIndex(accessString,v,dataSep,accessSep) ) ); 
67         }
68 
69         ref T opDispatch(string v)() if( getIndex(accessString,v,dataSep,accessSep) != -1 ) { 
70             mixin( format( "return this.%s[%d];", data, getIndex(accessString,v,dataSep,accessSep) ) ); 
71         }
72 
73         static if( isOneSymbolPerFieldForAnyAccessString(accessString,dataSep,accessSep) ) {
74             auto opDispatch(string v)() const 
75             if( v.length > 1 && oneOfAnyAccessAll(accessString,v,dataSep,accessSep) ) {
76                 static string gen() {
77                     string[] res;
78                     foreach( i, sym; v )
79                         res ~= format( "this.%s[%d]", data, getIndex( accessString, ""~sym, dataSep, accessSep ) );
80                     return res.join(",");
81                 }
82 
83                 mixin( `return Vector!(T, v.length)(` ~ gen() ~ `);` );
84             }
85 
86             auto opDispatch(string v,U)( in U b ) 
87             if( v.length > 1 && oneOfAnyAccessAll(accessString,v,dataSep,accessSep) && 
88                 isCompatibleArrayAccessString(v.length,v) && 
89             ( isSpecVector!(v.length,T,U) || ( isDynamicVector!U && is(typeof(T(U.datatype.init))) ) ) ) {
90                 static if( b.isDynamic ) enforce( v.length == b.length );
91 
92                 static string gen() {
93                     string[] res;
94                     foreach( i, sym; v )
95                         res ~= format( "this.%s[%d] = T( b[%d] );", data,
96                                     getIndex( accessString, ""~sym, dataSep, accessSep ), i );
97                     return res.join("\n");
98                 }
99 
100                 mixin( gen() );
101                 return b;
102             }
103         }
104     }
105 }
106 
107 /// compatible for creating access dispatches
108 pure bool isCompatibleArrayAccessStrings( size_t N, string str, string sep1="", string sep2="|" )
109 in { assert( sep1 != sep2 ); } do {
110     auto strs = str.split(sep2);
111     foreach( s; strs )
112         if( !isCompatibleArrayAccessString(N,s,sep1) )
113             return false;
114 
115     string[] fa;
116     foreach( s; strs )
117         fa ~= s.split(sep1);
118 
119     foreach( ref v; fa ) v = strip(v);
120 
121     foreach( i, a; fa )
122         foreach( j, b; fa )
123             if( i != j && a == b ) return false;
124 
125     return true;
126 }
127 
128 
129 /// compatible for creating access dispatches
130 pure bool isCompatibleArrayAccessString( size_t N, string str, string sep="" ) { 
131     return N == getAccessFieldsCount(str,sep) && isArrayAccessString(str,sep); 
132 }
133 
134 ///
135 pure bool isArrayAccessString( in string as, in string sep="", bool allowDot=false ) {
136     if( as.length == 0 ) return false;
137     auto splt = as.split(sep);
138     foreach( i, val; splt )
139         if( !isValueAccessString(val,allowDot) || canFind(splt[0..i],val) )
140             return false;
141     return true;
142 }
143 
144 ///
145 pure size_t getAccessFieldsCount( string str, string sep ) { return str.split(sep).length; }
146 
147 ///
148 pure ptrdiff_t getIndex( string as, string arg, string sep1="", string sep2="|" )
149 in { assert( sep1 != sep2 ); } do {
150     foreach( str; as.split(sep2) )
151         foreach( i, v; str.split(sep1) )
152             if( arg == v ) return i;
153     return -1;
154 }
155 
156 ///
157 pure bool oneOfAccess( string str, string arg, string sep="" ) {
158     auto splt = str.split(sep);
159     return canFind(splt,arg);
160 }
161 
162 ///
163 pure bool oneOfAccessAll( string str, string arg, string sep="" ) {
164     auto splt = arg.split("");
165     return all!(a=>oneOfAccess(str,a,sep))(splt);
166 }
167 
168 ///
169 pure bool oneOfAnyAccessAll( string str, string arg, string sep1="", string sep2="|" )
170 in { assert( sep1 != sep2 ); } do {
171     foreach( s; str.split(sep2) )
172         if( oneOfAccessAll(s,arg,sep1) ) return true;
173     return false;
174 }
175 
176 /// check symbol count for access to field
177 pure bool isOneSymbolPerFieldForAnyAccessString( string str, string sep1="", string sep2="|" )
178 in { assert( sep1 != sep2 ); } do {
179     foreach( s; str.split(sep2) )
180         if( isOneSymbolPerFieldAccessString(s,sep1) ) return true;
181     return false;
182 }
183 
184 /// check symbol count for access to field
185 pure bool isOneSymbolPerFieldAccessString( string str, string sep="" ) {
186     foreach( s; str.split(sep) )
187         if( s.length > 1 ) return false;
188     return true;
189 }
190 
191 pure {
192 
193     bool isValueAccessString( in string as, bool allowDot=false ) {
194         return as.length > 0 &&
195         startsWithAllowedChars(as) &&
196         (allowDot?(all!(a=>isValueAccessString(a))(as.split("."))):allowedCharsOnly(as));
197     }
198 
199     bool startsWithAllowedChars( in string as ) {
200         switch(as[0]) {
201             case 'a': .. case 'z': goto case;
202             case 'A': .. case 'Z': goto case;
203             case '_': return true;
204             default: return false;
205         }
206     }
207 
208     bool allowedCharsOnly( in string as ) {
209         foreach( c; as ) if( !allowedChar(c) ) return false;
210         return true;
211     }
212 
213     bool allowedChar( in char c ) {
214         switch(c) {
215             case 'a': .. case 'z': goto case;
216             case 'A': .. case 'Z': goto case;
217             case '0': .. case '9': goto case;
218             case '_': return true;
219             default: return false;
220         }
221     }
222 
223 }