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