1 module autowrap.csharp.dlang;
2 
3 import scriptlike : interp, _interp_text;
4 
5 import core.time : Duration;
6 import std.datetime : Date, DateTime, SysTime, TimeOfDay, TimeZone;
7 import std.ascii : newline;
8 import std.meta : allSatisfy;
9 
10 import autowrap.csharp.boilerplate;
11 import autowrap.csharp.common : getDLangInterfaceType;
12 import autowrap.reflection : isModule, PrimordialType;
13 
14 enum string methodSetup = "        thread_attachThis();
15         rt_moduleTlsCtor();
16         scope(exit) rt_moduleTlsDtor();
17         scope(exit) thread_detachThis();";
18 
19 
20 // Wrap global functions from multiple modules
21 public string wrapDLang(Modules...)() if(allSatisfy!(isModule, Modules)) {
22     import autowrap.csharp.common : isDateTimeType;
23     import autowrap.reflection : AllAggregates;
24     import std.traits : fullyQualifiedName, moduleName;
25     import std.meta : AliasSeq;
26 
27     string ret = string.init;
28     ret ~= "import core.thread : thread_attachThis, thread_detachThis;" ~ newline;
29     ret ~= "extern(C) void rt_moduleTlsCtor();" ~ newline;
30     ret ~= "extern(C) void rt_moduleTlsDtor();" ~ newline;
31     foreach(mod; Modules) {
32         ret ~= mixin(interp!"import ${mod.name};${newline}");
33     }
34     ret ~= newline;
35 
36     static foreach(t; AliasSeq!(string, wstring, dstring, bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, datetime)) {
37         ret ~= generateSliceMethods!t();
38     }
39 
40     alias aggregates = AllAggregates!Modules;
41     static foreach(agg; aggregates) {
42         static if (!isDateTimeType!agg) {
43             ret ~= generateSliceMethods!agg();
44             ret ~= generateConstructors!agg();
45             ret ~= generateMethods!agg();
46             ret ~= generateFields!agg();
47         }
48     }
49 
50     ret ~= generateFunctions!Modules();
51 
52     return ret;
53 }
54 
55 private string generateConstructors(T)() {
56     import autowrap.csharp.common : getDLangInterfaceName;
57     import std.traits : fullyQualifiedName, hasMember, Parameters, ParameterIdentifierTuple;
58     import std.meta : AliasSeq;
59     import std.algorithm : among;
60 
61     string ret = string.init;
62     alias fqn = getDLangInterfaceType!T;
63 
64     //Generate constructor methods
65     static if(hasMember!(T, "__ctor") && __traits(getProtection, __traits(getMember, T, "__ctor")).among("export", "public")) {
66         foreach(c; __traits(getOverloads, T, "__ctor")) {
67             if (__traits(getProtection, c).among("export", "public")) {
68                 alias paramNames = ParameterIdentifierTuple!c;
69                 alias paramTypes = Parameters!c;
70                 string exp = "extern(C) export ";
71                 const string interfaceName = getDLangInterfaceName(fqn, "__ctor");
72 
73                 exp ~= mixin(interp!"returnValue!(${fqn}) ${interfaceName}(");
74 
75                 static foreach(pc; 0..paramNames.length) {
76                     exp ~= mixin(interp!"${getDLangInterfaceType!(paramTypes[pc])} ${paramNames[pc]}, ");
77                 }
78                 if (paramTypes.length > 0) {
79                     exp = exp[0..$-2];
80                 }
81                 exp ~= ") nothrow {" ~ newline;
82                 exp ~= "    try {" ~ newline;
83                 exp ~= methodSetup ~ newline;
84                 if (is(T == class)) {
85                     exp ~= mixin(interp!"        ${fqn} __temp__ = new ${fqn}(");
86                     static foreach(pc; 0..paramNames.length) {
87                         exp ~= mixin(interp!"${paramNames[pc]}, ");
88                     }
89                     if (paramTypes.length > 0) {
90                         exp = exp[0..$-2];
91                     }
92                     exp ~= ");" ~ newline;
93                     exp ~= "        pinPointer(cast(void*)__temp__);" ~ newline;
94                     exp ~= mixin(interp!"        return returnValue!(${fqn})(__temp__);${newline}");
95                 } else if (is(T == struct)) {
96                     exp ~= mixin(interp!"        return ${fqn}(");
97                     foreach(pn; paramNames) {
98                         exp ~= mixin(interp!"${pn}, ");
99                     }
100                     if (paramTypes.length > 0) {
101                         exp = exp[0..$-2];
102                     }
103                     exp ~= ");" ~ newline;
104                 }
105                 exp ~= "    } catch (Exception __ex__) {" ~ newline;
106                 exp ~= mixin(interp!"        return returnValue!(${fqn})(__ex__);${newline}");
107                 exp ~= "    }" ~ newline;
108                 exp ~= "}" ~ newline;
109                 ret ~= exp;
110             }
111         }
112     }
113 
114     return ret;
115 }
116 
117 private string generateMethods(T)() {
118     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType, getDLangInterfaceName;
119     import std.traits : isFunction, fullyQualifiedName, ReturnType, Parameters, ParameterIdentifierTuple;
120     import std.conv : to;
121     import std.algorithm : among;
122 
123     string ret = string.init;
124     alias fqn = getDLangInterfaceType!T;
125     foreach(m; __traits(allMembers, T)) {
126         if (m.among("__ctor", "toHash", "opEquals", "opCmp", "factory")) {
127             continue;
128         }
129 
130         static if (is(typeof(__traits(getMember, T, m)))) {
131             foreach(oc, mo; __traits(getOverloads, T, m)) {
132                 const bool isMethod = isFunction!mo;
133 
134                 static if(isMethod && __traits(getProtection, mo).among("export", "public")) {
135                     string exp = string.init;
136                     const string interfaceName = getDLangInterfaceName(fqn, m);
137 
138                     alias returnType = ReturnType!mo;
139                     alias returnTypeStr = getDLangInterfaceType!returnType;
140                     alias paramTypes = Parameters!mo;
141                     alias paramNames = ParameterIdentifierTuple!mo;
142 
143                     exp ~= "extern(C) export ";
144                     static if (!is(returnType == void)) {
145                         exp ~= mixin(interp!"returnValue!(${returnTypeStr})");
146                     } else {
147                         exp ~= "returnVoid";
148                     }
149                     exp ~= mixin(interp!" ${interfaceName}${oc}(");
150                     if (is(T == struct)) {
151                         exp ~= mixin(interp!"ref ${fqn} __obj__, ");
152                     } else {
153                         exp ~= mixin(interp!"${fqn} __obj__, ");
154                     }
155                     static foreach(pc; 0..paramNames.length) {
156                         exp ~= mixin(interp!"${getDLangInterfaceType!(paramTypes[pc])} ${paramNames[pc]}, ");
157                     }
158                     exp = exp[0..$-2];
159                     exp ~= ") nothrow {" ~ newline;
160                     exp ~= "    try {" ~ newline;
161                     exp ~= methodSetup ~ newline;
162                     exp ~= "        ";
163                     if (!is(returnType == void)) {
164                         exp ~= "auto __result__ = ";
165                     }
166                     exp ~= mixin(interp!"__obj__.${m}(");
167                     static foreach(pc; 0..paramNames.length) {
168                         exp ~= mixin(interp!"${generateParameter!(paramTypes[pc])(paramNames[pc])}, ");
169                     }
170                     if (paramNames.length > 0) {
171                         exp = exp[0..$-2];
172                     }
173                     exp ~= ");" ~ newline;
174                     static if (isDateTimeType!returnType || isDateTimeArrayType!returnType) {
175                         exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(${generateReturn!returnType(\"__result__\")});${newline}");
176                     } else static if (!is(returnType == void)) {
177                         exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(__result__);${newline}");
178                     } else {
179                         exp ~= "        return returnVoid();" ~ newline;
180                     }
181                     exp ~= "    } catch (Exception __ex__) {" ~ newline;
182                     if (!is(returnType == void)) {
183                         exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(__ex__);${newline}");
184                     } else {
185                         exp ~= "        return returnVoid(__ex__);" ~ newline;
186                     }
187                     exp ~= "    }" ~ newline;
188                     exp ~= "}" ~ newline;
189                     ret ~= exp;
190                 }
191             }
192         }
193     }
194 
195     return ret;
196 }
197 
198 private string generateFields(T)() {
199     import autowrap.csharp.common : getDLangInterfaceName;
200     import std.traits : fullyQualifiedName, Fields, FieldNameTuple;
201 
202     string ret = string.init;
203     alias fqn = getDLangInterfaceType!T;
204     if (is(T == class) || is(T == interface)) {
205         alias fieldTypes = Fields!T;
206         alias fieldNames = FieldNameTuple!T;
207         static foreach(fc; 0..fieldTypes.length) {
208             static if (is(typeof(__traits(getMember, T, fieldNames[fc])))) {
209                 ret ~= mixin(interp!"extern(C) export returnValue!(${getDLangInterfaceType!(fieldTypes[fc])}) ${getDLangInterfaceName(fqn, fieldNames[fc] ~ \"_get\")}(${fqn} __obj__) nothrow {${newline}");
210                 ret ~= generateMethodErrorHandling(mixin(interp!"        auto __value__ = __obj__.${fieldNames[fc]};${newline}        return returnValue!(${getDLangInterfaceType!(fieldTypes[fc])})(${generateReturn!(fieldTypes[fc])(\"__value__\")});"), mixin(interp!"returnValue!(${getDLangInterfaceType!(fieldTypes[fc])})"));
211                 ret ~= "}" ~ newline;
212                 ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangInterfaceName(fqn, fieldNames[fc] ~ \"_set\")}(${fqn} __obj__, ${getDLangInterfaceType!(fieldTypes[fc])} value) nothrow {${newline}");
213                 ret ~= generateMethodErrorHandling(mixin(interp!"        __obj__.${fieldNames[fc]} = ${generateParameter!(fieldTypes[fc])(\"value\")};${newline}        return returnVoid();"), "returnVoid");
214                 ret ~= "}" ~ newline;
215             }
216         }
217     }
218     return ret;
219 }
220 
221 private string generateFunctions(Modules...)() if(allSatisfy!(isModule, Modules)) {
222     import autowrap.csharp.common : getDLangInterfaceName;
223     import autowrap.reflection: AllFunctions;
224     import std.traits : fullyQualifiedName, hasMember, functionAttributes, FunctionAttribute, ReturnType, Parameters, ParameterIdentifierTuple;
225 
226     string ret = string.init;
227     foreach(func; AllFunctions!Modules) {
228         alias modName = func.moduleName;
229         alias funcName = func.name;
230 
231         alias returnType = ReturnType!(__traits(getMember, func.module_, func.name));
232         alias returnTypeStr = getDLangInterfaceType!(ReturnType!(__traits(getMember, func.module_, func.name)));
233         alias paramTypes = Parameters!(__traits(getMember, func.module_, func.name));
234         alias paramNames = ParameterIdentifierTuple!(__traits(getMember, func.module_, func.name));
235         const string interfaceName = getDLangInterfaceName(modName, null, funcName);
236         string retType = string.init;
237         string funcStr = "extern(C) export ";
238 
239         static if (!is(returnType == void)) {
240             retType ~= mixin(interp!"returnValue!(${returnTypeStr})");
241         } else {
242             retType ~= "returnVoid";
243         }
244 
245         funcStr ~= mixin(interp!"${retType} ${interfaceName}(");
246         static foreach(pc; 0..paramNames.length) {
247             funcStr ~= mixin(interp!"${getDLangInterfaceType!(paramTypes[pc])} ${paramNames[pc]}, ");
248         }
249         if(paramNames.length > 0) {
250             funcStr = funcStr[0..$-2];
251         }
252         funcStr ~= ") nothrow {" ~ newline;
253         funcStr ~= "    try {" ~ newline;
254         funcStr ~= methodSetup ~ newline;
255         funcStr ~= "        ";
256         if (!is(returnType == void)) {
257             funcStr ~= mixin(interp!"auto __return__ = ${func.name}(");
258         } else {
259             funcStr ~= mixin(interp!"${func.name}(");
260         }
261         static foreach(pc; 0..paramNames.length) {
262             funcStr ~= mixin(interp!"${generateParameter!(paramTypes[pc])(paramNames[pc])}, ");
263         }
264         if(paramNames.length > 0) {
265             funcStr = funcStr[0..$-2];
266         }
267         funcStr ~= ");" ~ newline;
268         if (!is(returnType == void)) {
269             funcStr ~= mixin(interp!"        return ${retType}(${generateReturn!(returnType)(\"__return__\")});${newline}");
270         } else {
271             funcStr ~= mixin(interp!"        return ${retType}();${newline}");
272         }
273         funcStr ~= "    } catch (Exception __ex__) {" ~ newline;
274         funcStr ~= mixin(interp!"        return ${retType}(__ex__);${newline}");
275         funcStr ~= "    }" ~ newline;
276         funcStr ~= "}" ~ newline;
277 
278         ret ~= funcStr;
279     }
280 
281     return ret;
282 }
283 
284 private string generateSliceMethods(T)() {
285     import autowrap.csharp.common : getDLangSliceInterfaceName;
286     import std.traits : fullyQualifiedName;
287     string ret = string.init;
288     alias fqn = getDLangInterfaceType!T;
289 
290     //Generate slice creation method
291     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Create\")}(size_t capacity) nothrow {${newline}");
292     ret ~= generateMethodErrorHandling(mixin(interp!"        ${fqn}[] __temp__;${newline}        __temp__.reserve(capacity);${newline}        pinPointer(cast(void*)__temp__.ptr);${newline}        return returnValue!(${fqn}[])(__temp__);"), mixin(interp!"returnValue!(${fqn}[])"));
293     ret ~= "}" ~ newline;
294 
295     //Generate slice method
296     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Slice\")}(${fqn}[] slice, size_t begin, size_t end) nothrow {${newline}");
297     ret ~= generateMethodErrorHandling(mixin(interp!"        ${fqn}[] __temp__ = slice[begin..end];${newline}        pinPointer(cast(void*)__temp__.ptr);${newline}        return returnValue!(${fqn}[])(__temp__);"), mixin(interp!"returnValue!(${fqn}[])"));
298     ret ~= "}" ~ newline;
299 
300     //Generate get method
301     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}) ${getDLangSliceInterfaceName(fqn, \"Get\")}(${fqn}[] slice, size_t index) nothrow {${newline}");
302     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn})(slice[index]);"), mixin(interp!"returnValue!(${fqn})"));
303     ret ~= "}" ~ newline;
304 
305     //Generate set method
306     ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangSliceInterfaceName(fqn, \"Set\")}(${fqn}[] slice, size_t index, ${fqn} set) nothrow {${newline}");
307     ret ~= generateMethodErrorHandling(mixin(interp!"        slice[index] = set;${newline}        return returnVoid();"), "returnVoid");
308     ret ~= "}" ~ newline;
309 
310     //Generate item append method
311     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendValue\")}(${fqn}[] slice, ${fqn} append) nothrow {${newline}");
312     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
313     ret ~= "}" ~ newline;
314 
315     //Generate slice append method
316     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendSlice\")}(${fqn}[] slice, ${fqn}[] append) nothrow {${newline}");
317     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
318     ret ~= "}" ~ newline;
319 
320     return ret;
321 }
322 
323 private string generateMethodErrorHandling(string insideCode, string returnType) {
324     string ret = string.init;
325     ret ~= "    try {" ~ newline;
326     ret ~= methodSetup ~ newline;
327     ret ~= insideCode ~ newline;
328     ret ~= "    } catch (Exception __ex__) {" ~ newline;
329     ret ~= mixin(interp!"        return ${returnType}(__ex__);${newline}");
330     ret ~= "    }" ~ newline;
331     return ret;
332 }
333 
334 private string generateParameter(T)(string name) {
335     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
336     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
337     import std.traits : fullyQualifiedName;
338 
339     alias fqn = fullyQualifiedName!(PrimordialType!T);
340     static if (isDateTimeType!T) {
341         return mixin(interp!"fromDatetime!(${fqn})(${name})");
342     } else static if (isDateTimeArrayType!T) {
343         return mixin(interp!"fromDatetime1DArray!(${fqn})(${name})");
344     } else {
345         return name;
346     }
347 }
348 
349 private string generateReturn(T)(string name) {
350     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
351     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
352     import std.traits : fullyQualifiedName;
353 
354     alias fqn = fullyQualifiedName!(PrimordialType!T);
355     static if (isDateTimeType!T) {
356         return mixin(interp!"toDatetime!(${fqn})(${name})");
357     } else static if (isDateTimeArrayType!T) {
358         return mixin(interp!"toDatetime1DArray!(${fqn})(${name})");
359     } else {
360         return name;
361     }
362 }