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 }