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