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