1 /** 2 Wrapping functionality for D to Python. 3 */ 4 module autowrap.pynih.wrap; 5 6 7 public import std.typecons: Yes, No; 8 public import autowrap.types: Modules, Module, isModule, 9 LibraryName, PreModuleInitCode, PostModuleInitCode, RootNamespace, Ignore; 10 static import python.boilerplate; 11 import python.raw: isPython2, isPython3; 12 import std.meta: allSatisfy; 13 14 15 /** 16 Returns a string to mixin that implements the necessary boilerplate 17 to create a Python library containing one Python module 18 wrapping all relevant D code and data structures. 19 */ 20 string wrapDlang( 21 LibraryName libraryName, 22 Modules modules, 23 RootNamespace _ = RootNamespace(), // ignored in this backend 24 PreModuleInitCode preModuleInitCode = PreModuleInitCode(), // ignored in this backend 25 PostModuleInitCode postModuleInitCode = PostModuleInitCode(), // ignored in this backend 26 ) 27 () 28 { 29 return __ctfe 30 ? createPythonModuleMixin!(libraryName, modules) 31 : null; 32 } 33 34 35 string createPythonModuleMixin(LibraryName libraryName, Modules modules) 36 () 37 { 38 import std.format: format; 39 import std.algorithm: map; 40 import std.array: join; 41 42 if(!__ctfe) return null; 43 44 const modulesList = modules.value.map!(a => a.toString).join(", "); 45 46 return q{ 47 import python.raw: PyDateTime_CAPI; 48 49 // This is declared as an extern C variable in python.bindings. 50 // We declare it here to avoid linker errors. 51 export __gshared extern(C) PyDateTime_CAPI* PyDateTimeAPI; 52 53 extern(C) export auto %s() { // -> ModuleInitRet 54 import autowrap.pynih.wrap: createPythonModule, LibraryName; 55 import autowrap.types: Module; 56 return createPythonModule!( 57 LibraryName("%s"), 58 %s 59 ); 60 } 61 }.format( 62 pyInitFuncName(libraryName), // extern(C) function name 63 libraryName.value, 64 modulesList, 65 ); 66 } 67 68 69 private string pyInitFuncName(LibraryName libraryName) @safe pure nothrow { 70 71 string prefix() { 72 static if(isPython2) 73 return "init"; 74 else static if(isPython3) 75 return "PyInit_"; 76 else 77 static assert(false); 78 } 79 80 return prefix ~ libraryName.value; 81 } 82 83 84 auto createPythonModule(LibraryName libraryName, modules...)() 85 if(allSatisfy!(isModule, modules)) 86 { 87 import autowrap.common: toSnakeCase; 88 import python.type: PythonFunction; 89 import python.boilerplate: Module, CFunctions, CFunction, Aggregates; 90 import python.raw: PyModule_AddIntConstant, PyModule_AddStringConstant; 91 import autowrap.reflection: AllAggregates, AllFunctions, AllConstants; 92 import std.meta: Filter, templateNot, staticMap; 93 import std.traits: fullyQualifiedName; 94 95 static immutable char[1] emptyString = ['\0']; 96 97 alias allFunctions = AllFunctions!modules; 98 enum isWrappableFunction(alias functionSymbol) = 99 __traits(compiles, &PythonFunction!(functionSymbol.symbol)._py_function_impl); 100 alias wrappableFunctions = Filter!(isWrappableFunction, allFunctions); 101 alias nonWrappableFunctions = Filter!(templateNot!isWrappableFunction, allFunctions); 102 103 static foreach(nonWrappableFunction; nonWrappableFunctions) {{ 104 pragma(msg, "autowrap WARNING: Could not wrap function ", 105 fullyQualifiedName!(nonWrappableFunction.symbol)); 106 // uncomment to see the compiler error 107 // auto ptr = &PythonFunction!(nonWrappableFunction.symbol)._py_function_impl; 108 }} 109 110 alias toCFunction(alias functionSymbol) = CFunction!( 111 PythonFunction!(functionSymbol.symbol)._py_function_impl, 112 functionSymbol.identifier.toSnakeCase, 113 ); 114 alias cfunctions = CFunctions!(staticMap!(toCFunction, wrappableFunctions)); 115 116 alias allAggregates = AllAggregates!modules; 117 alias aggregates = Aggregates!allAggregates; 118 119 enum pythonModule = python.boilerplate.Module(libraryName.value); 120 121 mixin createPythonModule!(pythonModule, cfunctions, aggregates); 122 auto ret = _py_init_impl(); 123 124 template isIntegral(alias var) { 125 import std.traits: _isIntegral = isIntegral; 126 enum isIntegral = _isIntegral!(var.Type); 127 } 128 129 enum isString(alias var) = is(var.Type == string); 130 131 alias constants = AllConstants!modules; 132 static foreach(intConstant; Filter!(isIntegral, constants)) { 133 PyModule_AddIntConstant(ret, &intConstant.identifier[0], intConstant.value); 134 } 135 static foreach(strConstant; Filter!(isString, constants)) { 136 // We can't pass a null pointer if the value of the string constant is empty 137 PyModule_AddStringConstant(ret, 138 &strConstant.identifier[0], 139 strConstant.value.length ? &strConstant.value[0] : &emptyString[0]); 140 } 141 142 return ret; 143 } 144 145 146 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates) 147 if(isPython3) 148 { 149 static extern(C) export auto _py_init_impl() { // -> ModuleInitRet 150 import python.raw: pyDateTimeImport; 151 import python.cooked: createModule; 152 import core.runtime: rt_init; 153 154 rt_init; 155 156 pyDateTimeImport; 157 return createModule!(module_, cfunctions, aggregates); 158 } 159 } 160 161 162 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates) 163 if(isPython2) 164 { 165 static extern(C) export void _py_init_impl() { 166 import python.raw: pyDateTimeImport; 167 import python.cooked: initModule; 168 import core.runtime: rt_init; 169 170 rt_init; 171 172 pyDateTimeImport; 173 initModule!(module_, cfunctions, aggregates); 174 } 175 }