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