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.reflection: Modules, Module, isModule;
9 public import autowrap.types: LibraryName, PreModuleInitCode, PostModuleInitCode;
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     PreModuleInitCode preModuleInitCode = PreModuleInitCode(),
24     PostModuleInitCode postModuleInitCode = PostModuleInitCode(),
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.reflection: 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 
70     string prefix() {
71         static if(isPython2)
72             return "init";
73         else static if(isPython3)
74             return "PyInit_";
75         else
76             static assert(false);
77     }
78 
79     return prefix ~ libraryName.value;
80 }
81 
82 
83 auto createPythonModule(LibraryName libraryName, modules...)()
84     if(allSatisfy!(isModule, modules))
85 {
86     import autowrap.common: toSnakeCase;
87     import python.type: PythonFunction;
88     import python.boilerplate: Module, CFunctions, CFunction, Aggregates;
89     import autowrap.reflection: AllAggregates, AllFunctions;
90     import std.meta: Filter, templateNot, staticMap;
91     import std.traits: fullyQualifiedName;
92 
93     alias allFunctions = AllFunctions!modules;
94     enum isWrappableFunction(alias functionSymbol) =
95         __traits(compiles, &PythonFunction!(functionSymbol.symbol)._py_function_impl);
96     alias wrappableFunctions = Filter!(isWrappableFunction, allFunctions);
97     alias nonWrappableFunctions = Filter!(templateNot!isWrappableFunction, allFunctions);
98 
99     static foreach(nonWrappableFunction; nonWrappableFunctions) {
100         pragma(msg, "autowrap WARNING: Could not wrap function ", fullyQualifiedName!nonWrappableFunction);
101     }
102 
103     alias toCFunction(alias functionSymbol) = CFunction!(
104         PythonFunction!(functionSymbol.symbol)._py_function_impl,
105         functionSymbol.name.toSnakeCase,
106     );
107     alias cfunctions = CFunctions!(staticMap!(toCFunction, wrappableFunctions));
108 
109     alias allAggregates = AllAggregates!modules;
110     alias aggregates = Aggregates!allAggregates;
111 
112     enum pythonModule = python.boilerplate.Module(libraryName.value);
113 
114     mixin createPythonModule!(pythonModule, cfunctions, aggregates);
115     return _py_init_impl;
116 }
117 
118 
119 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates)
120     if(isPython3)
121 {
122     static extern(C) export auto _py_init_impl() {  // -> ModuleInitRet
123         import python.raw: pyDateTimeImport;
124         import python.cooked: createModule;
125         import core.runtime: rt_init;
126 
127         rt_init;
128 
129         pyDateTimeImport;
130         return createModule!(module_, cfunctions, aggregates);
131     }
132 }
133 
134 
135 mixin template createPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates)
136     if(isPython2)
137 {
138     static extern(C) export void _py_init_impl() {
139         import python.raw: pyDateTimeImport;
140         import python.cooked: initModule;
141         import core.runtime: rt_init;
142 
143         rt_init;
144 
145         pyDateTimeImport;
146         initModule!(module_, cfunctions, aggregates);
147     }
148 }