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