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