1 /** 2 Functions to wrap entities in D modules for Python consumption. 3 4 These functions are usually not called directly, but from the mixin generated by 5 autowrap.python.boilerplate.pydBoilerplate. 6 */ 7 module autowrap.python.wrap; 8 9 import autowrap.types: isModule; 10 import autowrap.reflection: isUserAggregate; 11 import std.meta: allSatisfy; 12 import std.traits: isArray; 13 14 15 private alias I(alias T) = T; 16 private enum isString(alias T) = is(typeof(T) == string); 17 18 /// Wrap global functions from multiple modules 19 void wrapAllFunctions(Modules...)() if(allSatisfy!(isModule, Modules)) { 20 import autowrap.common: toSnakeCase; 21 import autowrap.reflection: AllFunctions; 22 import pyd.pyd: def, PyName; 23 24 static foreach(function_; AllFunctions!Modules) { 25 static if(__traits(compiles, def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))())) 26 def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))(); 27 else { 28 pragma(msg, "\nERROR! Autowrap could not wrap function `", function_.identifier, "` for Python\n"); 29 // def!(function_.symbol, PyName!(toSnakeCase(function_.identifier)))(); 30 } 31 } 32 } 33 34 35 /** 36 wrap all aggregates found in the given modules, specified by their name 37 (to avoid importing all of them first). 38 39 This function wraps all struct and class definitions, and also all struct and class 40 types that are parameters or return types of any functions found. 41 */ 42 void wrapAllAggregates(Modules...)() if(allSatisfy!(isModule, Modules)) { 43 44 import autowrap.reflection: AllAggregates; 45 import std.traits: fullyQualifiedName; 46 47 static foreach(aggregate; AllAggregates!Modules) { 48 static if(__traits(compiles, wrapAggregate!aggregate)) 49 wrapAggregate!aggregate; 50 else { 51 pragma(msg, "\nERROR! Autowrap could not wrap aggregate `", fullyQualifiedName!aggregate, "` for Python\n"); 52 // wrapAggregate!aggregate; // uncomment to see the error messages from the compiler 53 } 54 } 55 } 56 57 58 /** 59 Wrap aggregate of type T. 60 */ 61 auto wrapAggregate(T)() if(isUserAggregate!T) { 62 63 import autowrap.python.pyd.class_wrap: MemberFunction; 64 import mirror.traits: isProperty, isStaticMemberFunction, PublicFieldNames; 65 import pyd.pyd: wrap_class, Member, Init, StaticDef, Repr, Property; 66 import std.meta: staticMap, Filter, templateNot, AliasSeq; 67 import std.algorithm: startsWith; 68 69 alias AggMember(string memberName) = Symbol!(T, memberName); 70 alias members = staticMap!(AggMember, __traits(allMembers, T)); 71 alias memberFunctions = Filter!(isMemberFunction, members); 72 alias staticMemberFunctions = Filter!(isStaticMemberFunction, memberFunctions); 73 alias nonStaticMemberFunctions = Filter!(templateNot!isStaticMemberFunction, memberFunctions); 74 enum isOperator(alias F) = __traits(identifier, F).startsWith("op"); 75 alias regularMemberFunctions = Filter!(templateNot!isOperator, Filter!(templateNot!isProperty, nonStaticMemberFunctions)); 76 alias properties = Filter!(isProperty, nonStaticMemberFunctions); 77 // FIXME - See #54 78 static if(is(T == class)) 79 alias publicFieldNames = AliasSeq!(); 80 else 81 alias publicFieldNames = PublicFieldNames!T; 82 83 enum isToString(alias F) = __traits(identifier, F) == "toString"; 84 85 wrap_class!( 86 T, 87 staticMap!(Member, publicFieldNames), 88 staticMap!(MemberFunction, regularMemberFunctions), 89 staticMap!(StaticDef, staticMemberFunctions), 90 staticMap!(InitTuple, ConstructorParamTuples!T), 91 staticMap!(Repr, Filter!(isToString, memberFunctions)), 92 staticMap!(Property, properties), 93 OpUnaries!T, 94 OpBinaries!T, 95 OpBinaryRights!T, 96 OpCmps!T, 97 Lengths!T, 98 OpIndices!T, 99 DefOpSlices!T, 100 OpSliceRanges!T, 101 OpOpAssigns!T, 102 OpIndexAssigns!T, 103 OpSliceAssigns!T, 104 OpCalls!T, 105 ); 106 } 107 108 109 // Given a parent (module, struct, ...) and a memberName, alias the actual member, 110 // or void if not possible 111 private template Symbol(alias parent, string memberName) { 112 static if(__traits(compiles, I!(__traits(getMember, parent, memberName)))) 113 alias Symbol = I!(__traits(getMember, parent, memberName)); 114 else 115 alias Symbol = void; 116 } 117 118 119 private template DefOpSlices(T) { 120 import std.traits: hasMember, Parameters; 121 import std.meta: AliasSeq, Filter, staticMap; 122 123 static if(hasMember!(T, "opSlice")) { 124 // See testdll for details on this 125 enum hasNoParams(alias F) = Parameters!F.length == 0; 126 alias iters = Filter!(hasNoParams, __traits(getOverloads, T, "opSlice")); 127 alias defIters = staticMap!(DefOpSlice, iters); 128 129 alias DefOpSlices = AliasSeq!(defIters); 130 } else 131 alias DefOpSlices = AliasSeq!(); 132 } 133 134 private template DefOpSlice(alias F) { 135 import pyd.pyd: Def, PyName; 136 import std.traits: ReturnType, Parameters; 137 alias DefOpSlice = Def!(F, PyName!"__iter__", ReturnType!F function(Parameters!F)); 138 } 139 140 private template OpSliceRanges(T) { 141 import pyd.pyd: OpSlice; 142 import std.traits: hasMember, Parameters, isIntegral; 143 import std.meta: AliasSeq, Filter, allSatisfy, staticMap; 144 145 static if(hasMember!(T, "opSlice")) { 146 enum hasTwoIntParams(alias F) = 147 allSatisfy!(isIntegral, Parameters!F) && Parameters!F.length == 2; 148 alias twoInts = Filter!(hasTwoIntParams, __traits(getOverloads, T, "opSlice")); 149 150 static if(twoInts.length > 0) { 151 // pyd is very specific about this for some reason 152 static if(__traits(compiles, OpSlice!().Inner!T)) 153 alias OpSliceRanges = OpSlice!(); 154 else 155 alias OpSliceRanges = AliasSeq!(); 156 } else 157 alias OpSliceRanges = AliasSeq!(); 158 } else 159 alias OpSliceRanges = AliasSeq!(); 160 } 161 162 163 164 // A tuple, with as many elements as constructors. Each element is a 165 // std.typecons.Tuple of the constructor parameter types. 166 private template ConstructorParamTuples(alias T) { 167 import std.meta: staticMap, AliasSeq; 168 import std.traits: Parameters, hasMember; 169 import std.typecons: Tuple; 170 171 // If we staticMap with std.traits.Parameters, we end up with a collapsed tuple 172 // i.e. with one constructor that takes int and another that takes int, string, 173 // we'd end up with 3 elements (int, int, string) instead of 2 ((int), (int, string)) 174 // so we package them up in a std.typecons.Tuple to avoid flattening 175 // each being an AliasSeq of types for the constructor 176 alias ParametersTuple(alias F) = Tuple!(Parameters!F); 177 178 static if(hasMember!(T, "__ctor")) 179 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor")); 180 else 181 alias constructors = AliasSeq!(); 182 183 // A tuple, with as many elements as constructors. Each element is a 184 // std.typecons.Tuple of the constructor parameter types. 185 alias ConstructorParamTuples = staticMap!(ParametersTuple, constructors); 186 } 187 188 // Apply pyd's Init to the unpacked types of the parameter Tuple. 189 private template InitTuple(alias Tuple) { 190 import pyd.pyd: Init; 191 alias InitTuple = Init!(Tuple.Types); 192 } 193 194 195 private alias OpBinaries(T) = Operators!(T, "opBinary"); 196 private alias OpBinaryRights(T) = Operators!(T, "opBinaryRight"); 197 private alias OpUnaries(T) = Operators!(T, "opUnary"); 198 private alias OpOpAssigns(T) = Operators!(T, "opOpAssign"); 199 200 private template Operators(T, string name) { 201 import std.uni: toUpper; 202 import std.conv: text; 203 204 private enum pascalName = name[0].toUpper.text ~ name[1..$]; 205 static if(pascalName == "OpOpAssign") 206 private enum pydName = "OpAssign"; 207 else 208 private enum pydName = pascalName; 209 210 mixin(`import pyd.pyd: ` ~ pydName ~ `;`); 211 import std.meta: AliasSeq, staticMap, Filter; 212 import std.traits: hasMember; 213 214 private enum hasOperator(string op) = is(typeof(probeTemplate!(T, name, op))); 215 mixin(`alias toPyd(string op) = ` ~ pydName ~ `!op;`); 216 217 alias pythonableOperators = AliasSeq!( 218 "+", "-", "*", "/", "%", "^^", "<<", ">>", "&", "^", "|", "in", "~", 219 ); 220 221 static if(hasMember!(T, name)) { 222 private alias dOperatorNames = Filter!(hasOperator, pythonableOperators); 223 alias Operators = staticMap!(toPyd, dOperatorNames); 224 } else 225 alias Operators = AliasSeq!(); 226 } 227 228 229 private auto probeTemplate(T, string templateName, string op)() { 230 import std.traits: ReturnType, Parameters; 231 import std.meta: Alias; 232 233 mixin(`alias func = T.` ~ templateName ~ `;`); 234 alias R = ReturnType!(func!op); 235 alias P = Parameters!(func!op); 236 237 auto obj = T.init; 238 239 static if(is(R == void)) 240 mixin(`obj.` ~ templateName ~ `!op(P.init);`); 241 else 242 mixin(`R ret = obj.` ~ templateName ~ `!op(P.init);`); 243 } 244 245 246 private template OpCmps(T) { 247 import pyd.pyd: OpCompare; 248 import std.traits: hasMember; 249 import std.meta: AliasSeq; 250 251 static if(hasMember!(T, "opCmp")) { 252 static if(__traits(compiles, OpCompare!().Inner!T)) 253 alias OpCmps = AliasSeq!(OpCompare!()); 254 else 255 alias OpCmps = AliasSeq!(); 256 } else 257 alias OpCmps = AliasSeq!(); 258 } 259 260 private template Lengths(T) { 261 import pyd.pyd: Len; 262 import std.meta: AliasSeq; 263 264 static if(is(typeof(T.init.length))) 265 alias Lengths = Len!(T.length); 266 else 267 alias Lengths = AliasSeq!(); 268 } 269 270 private template OpIndices(T) { 271 import pyd.pyd: OpIndex; 272 import std.meta: AliasSeq; 273 274 static if(is(typeof(T.init.opIndex(0)))) 275 alias OpIndices = OpIndex!(); 276 else 277 alias OpIndices = AliasSeq!(); 278 } 279 280 281 private template OpIndexAssigns(T) { 282 import pyd.pyd: OpIndexAssign; 283 import std.meta: AliasSeq; 284 import std.traits: hasMember; 285 286 static if(hasMember!(T, "opIndexAssign")) { 287 static if(__traits(compiles, OpIndexAssign!().Inner!T)) 288 alias OpIndexAssigns = OpIndexAssign!(); 289 else 290 alias OpIndexAssigns = AliasSeq!(); 291 } else 292 alias OpIndexAssigns = AliasSeq!(); 293 } 294 295 296 private template OpSliceAssigns(T) { 297 import pyd.pyd: OpSliceAssign; 298 import std.meta: AliasSeq; 299 import std.traits: hasMember; 300 301 static if(hasMember!(T, "opSliceAssign")) { 302 static if(__traits(compiles, OpSliceAssign!().Inner!T)) 303 alias OpSliceAssigns = OpSliceAssign!(); 304 else 305 alias OpSliceAssigns = AliasSeq!(); 306 } else 307 alias OpSliceAssigns = AliasSeq!(); 308 } 309 310 311 private template OpCalls(T) { 312 import pyd.pyd: OpCall; 313 import std.meta: AliasSeq, staticMap; 314 import std.traits: hasMember, Parameters; 315 316 static if(hasMember!(T, "opCall")) { 317 alias overloads = AliasSeq!(__traits(getOverloads, T, "opCall")); 318 alias opCall(alias F) = OpCall!(Parameters!F); 319 alias OpCalls = staticMap!(opCall, overloads); 320 } else 321 alias OpCalls = AliasSeq!(); 322 } 323 324 325 // must be a global template 326 private template isMemberFunction(A...) if(A.length == 1) { 327 import std.algorithm: startsWith; 328 329 alias T = A[0]; 330 331 static if(__traits(compiles, __traits(identifier, T))) { 332 enum name = __traits(identifier, T); 333 enum isMemberFunction = 334 isPublicFunction!T 335 && !name.startsWith("__") 336 && name != "toHash" 337 ; 338 } else 339 enum isMemberFunction = false; 340 } 341 342 343 private template isPublicFunction(alias F) { 344 import std.traits: isFunction; 345 enum prot = __traits(getProtection, F); 346 enum isPublicFunction = isFunction!F && (prot == "export" || prot == "public"); 347 }