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 import std.range.primitives;
10 
11 import autowrap.csharp.boilerplate;
12 import autowrap.reflection : isModule, PrimordialType;
13 
14 enum string methodSetup = "        auto attachThread = AttachThread.create();";
15 
16 
17 // Wrap global functions from multiple modules
18 public string wrapDLang(Modules...)() if(allSatisfy!(isModule, Modules)) {
19     import autowrap.csharp.common : isDateTimeType, verifySupported;
20     import autowrap.reflection : AllAggregates;
21 
22     import std.algorithm.iteration : map;
23     import std.algorithm.sorting : sort;
24     import std.array : array;
25     import std.format : format;
26     import std.meta : AliasSeq;
27     import std.traits : fullyQualifiedName;
28 
29     string ret;
30     string[] imports = [Modules].map!(a => a.name)().array();
31 
32     static foreach(T; AliasSeq!(string, wstring, dstring, bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, datetime)) {
33         ret ~= generateSliceMethods!T(imports);
34     }
35 
36     static foreach(Agg; AllAggregates!Modules)
37     {
38         static if(verifySupported!Agg && !isDateTimeType!Agg)
39         {
40             ret ~= generateSliceMethods!Agg(imports);
41             ret ~= generateConstructors!Agg(imports);
42             ret ~= generateMethods!Agg(imports);
43             ret ~= generateFields!Agg(imports);
44         }
45     }
46 
47     ret ~= generateFunctions!Modules(imports);
48 
49     string top = "import autowrap.csharp.boilerplate : AttachThread;" ~ newline;
50 
51     foreach(i; sort(imports))
52         top ~= format("import %s;%s", i, newline);
53 
54     return top ~ "\n" ~ ret;
55 }
56 
57 // This is to deal with the cases where the parameter name is the same as a
58 // module or pacakage name, which results in errors if the full path name is
59 // used inside the function (e.g. in the return type is prefix.Prefix, and the
60 // parameter is named prefix).
61 private enum AdjParamName(string paramName) = paramName ~ "_param";
62 
63 private string generateConstructors(T)(ref string[] imports)
64 {
65     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
66 
67     import std.algorithm.comparison : among;
68     import std.conv : to;
69     import std.format : format;
70     import std.meta : Filter, staticMap;
71     import std.traits : fullyQualifiedName, hasMember, Parameters, ParameterIdentifierTuple;
72 
73     string ret;
74     alias fqn = getDLangInterfaceType!T;
75 
76     //Generate constructor methods
77     static if(hasMember!(T, "__ctor") && __traits(getProtection, __traits(getMember, T, "__ctor")).among("export", "public"))
78     {
79         foreach(i, c; __traits(getOverloads, T, "__ctor"))
80         {
81             if (__traits(getProtection, c).among("export", "public"))
82             {
83                 alias paramNames = staticMap!(AdjParamName, ParameterIdentifierTuple!c);
84                 alias ParamTypes = Parameters!c;
85 
86                 static if(Filter!(verifySupported, ParamTypes).length != ParamTypes.length)
87                     continue;
88                 addImports!ParamTypes(imports);
89 
90                 static foreach(nda; 0 .. numDefaultArgs!c + 1)
91                 {{
92                     enum numParams = ParamTypes.length - nda;
93                     enum interfaceName = format("%s%s_%s", getDLangInterfaceName(fqn, "__ctor"), i, numParams);
94 
95                     string exp = "extern(C) export ";
96                     exp ~= mixin(interp!"returnValue!(${fqn}) ${interfaceName}(");
97 
98                     static foreach(pc; 0 .. paramNames.length - nda)
99                         exp ~= mixin(interp!"${getDLangInterfaceType!(ParamTypes[pc])} ${paramNames[pc]}, ");
100 
101                     if (numParams != 0)
102                         exp = exp[0 .. $ - 2];
103 
104                     exp ~= ") nothrow {" ~ newline;
105                     exp ~= "    try {" ~ newline;
106                     exp ~= methodSetup ~ newline;
107                     if (is(T == class))
108                     {
109                         exp ~= mixin(interp!"        ${fqn} __temp__ = new ${fqn}(");
110 
111                         static foreach(pc; 0 .. numParams)
112                             exp ~= mixin(interp!"${paramNames[pc]}, ");
113 
114                         if (numParams != 0)
115                             exp = exp[0 .. $ - 2];
116 
117                         exp ~= ");" ~ newline;
118                         exp ~= "        pinPointer(cast(void*)__temp__);" ~ newline;
119                         exp ~= mixin(interp!"        return returnValue!(${fqn})(__temp__);${newline}");
120                     }
121                     else if (is(T == struct))
122                     {
123                         exp ~= mixin(interp!"        return returnValue!(${fqn})(${fqn}(");
124 
125                         foreach(pn; paramNames)
126                             exp ~= mixin(interp!"${pn}, ");
127 
128                         if (numParams != 0)
129                             exp = exp[0 .. $ - 2];
130 
131                         exp ~= "));" ~ newline;
132                     }
133 
134                     exp ~= "    } catch (Exception __ex__) {" ~ newline;
135                     exp ~= mixin(interp!"        return returnValue!(${fqn})(__ex__);${newline}");
136                     exp ~= "    }" ~ newline;
137                     exp ~= "}" ~ newline;
138                     ret ~= exp;
139                 }}
140             }
141         }
142     }
143 
144     return ret;
145 }
146 
147 private string generateMethods(T)(ref string[] imports)
148 {
149     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType, getDLangInterfaceName,
150                                     numDefaultArgs, verifySupported;
151 
152     import std.algorithm.comparison : among;
153     import std.format : format;
154     import std.meta : AliasSeq, Filter, staticMap;
155     import std.traits : isFunction, fullyQualifiedName, ReturnType, Parameters, ParameterIdentifierTuple;
156 
157     string ret;
158     alias fqn = getDLangInterfaceType!T;
159 
160     foreach(m; __traits(allMembers, T))
161     {
162         static if (!m.among("__ctor", "toHash", "opEquals", "opCmp", "factory") &&
163                    is(typeof(__traits(getMember, T, m))))
164         {
165             foreach(oc, mo; __traits(getOverloads, T, m))
166             {
167                 static if(isFunction!mo && __traits(getProtection, mo).among("export", "public"))
168                 {
169                     alias RT = ReturnType!mo;
170                     alias returnTypeStr = getDLangInterfaceType!RT;
171                     alias ParamTypes = Parameters!mo;
172                     alias paramNames = staticMap!(AdjParamName, ParameterIdentifierTuple!mo);
173                     alias Types = AliasSeq!(RT, ParamTypes);
174 
175                     static if(Filter!(verifySupported, Types).length != Types.length)
176                         continue;
177                     else
178                     {
179                         addImports!Types(imports);
180 
181                         static foreach(nda; 0 .. numDefaultArgs!mo + 1)
182                         {{
183                             enum numParams = ParamTypes.length - nda;
184                             enum interfaceName = format("%s%s_%s", getDLangInterfaceName(fqn, m), oc, numParams);
185 
186                             string exp = "extern(C) export ";
187 
188                             static if (!is(RT == void))
189                                 exp ~= mixin(interp!"returnValue!(${returnTypeStr})");
190                             else
191                                 exp ~= "returnVoid";
192 
193                             exp ~= mixin(interp!" ${interfaceName}(");
194 
195                             if (is(T == struct))
196                                 exp ~= mixin(interp!"ref ${fqn} __obj__, ");
197                             else
198                                 exp ~= mixin(interp!"${fqn} __obj__, ");
199 
200                             static foreach(pc; 0 .. numParams)
201                                 exp ~= mixin(interp!"${getDLangInterfaceType!(ParamTypes[pc])} ${paramNames[pc]}, ");
202 
203                             exp = exp[0 .. $ - 2];
204                             exp ~= ") nothrow {" ~ newline;
205                             exp ~= "    try {" ~ newline;
206                             exp ~= methodSetup ~ newline;
207                             exp ~= "        ";
208 
209                             if (!is(RT == void))
210                                 exp ~= "auto __result__ = ";
211 
212                             exp ~= mixin(interp!"__obj__.${m}(");
213 
214                             static foreach(pc; 0 .. numParams)
215                                 exp ~= mixin(interp!"${generateParameter!(ParamTypes[pc])(paramNames[pc])}, ");
216 
217                             if (numParams != 0)
218                                 exp = exp[0 .. $ - 2];
219 
220                             exp ~= ");" ~ newline;
221 
222                             static if (isDateTimeType!RT || isDateTimeArrayType!RT)
223                                 exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(${generateReturn!RT(\"__result__\")});${newline}");
224                             else static if (!is(RT == void))
225                                 exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(__result__);${newline}");
226                             else
227                                 exp ~= "        return returnVoid();" ~ newline;
228 
229                             exp ~= "    } catch (Exception __ex__) {" ~ newline;
230 
231                             if (!is(RT == void))
232                                 exp ~= mixin(interp!"        return returnValue!(${returnTypeStr})(__ex__);${newline}");
233                             else
234                                 exp ~= "        return returnVoid(__ex__);" ~ newline;
235 
236                             exp ~= "    }" ~ newline;
237                             exp ~= "}" ~ newline;
238                             ret ~= exp;
239                         }}
240                     }
241                 }
242             }
243         }
244     }
245 
246     return ret;
247 }
248 
249 private string generateFields(T)(ref string[] imports) {
250     import autowrap.csharp.common : getDLangInterfaceName, verifySupported;
251 
252     import std.traits : fullyQualifiedName, Fields, FieldNameTuple;
253 
254     string ret;
255     alias fqn = getDLangInterfaceType!T;
256     if (is(T == class) || is(T == interface))
257     {
258         alias FieldTypes = Fields!T;
259         alias fieldNames = FieldNameTuple!T;
260         static foreach(fc; 0 .. FieldTypes.length)
261         {{
262             alias FT = FieldTypes[fc];
263             static if(verifySupported!FT)
264             {
265                 alias fn = fieldNames[fc];
266                 static if (is(typeof(__traits(getMember, T, fn))))
267                 {
268                     addImport!FT(imports);
269 
270                     ret ~= mixin(interp!"extern(C) export returnValue!(${getDLangInterfaceType!FT}) ${getDLangInterfaceName(fqn, fn ~ \"_get\")}(${fqn} __obj__) nothrow {${newline}");
271                     ret ~= generateMethodErrorHandling(mixin(interp!"        auto __value__ = __obj__.${fn};${newline}        return returnValue!(${getDLangInterfaceType!FT})(${generateReturn!FT(\"__value__\")});"), mixin(interp!"returnValue!(${getDLangInterfaceType!FT})"));
272                     ret ~= "}" ~ newline;
273                     ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangInterfaceName(fqn, fn ~ \"_set\")}(${fqn} __obj__, ${getDLangInterfaceType!FT} value) nothrow {${newline}");
274                     ret ~= generateMethodErrorHandling(mixin(interp!"        __obj__.${fn} = ${generateParameter!FT(\"value\")};${newline}        return returnVoid();"), "returnVoid");
275                     ret ~= "}" ~ newline;
276                 }
277             }
278         }}
279     }
280     return ret;
281 }
282 
283 private string generateFunctions(Modules...)(ref string[] imports)
284     if(allSatisfy!(isModule, Modules))
285 {
286     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
287     import autowrap.reflection: AllFunctions;
288 
289     import std.format : format;
290     import std.meta : AliasSeq, Filter, staticMap;
291     import std.traits : fullyQualifiedName, hasMember, functionAttributes, FunctionAttribute,
292                         ReturnType, Parameters, ParameterIdentifierTuple;
293 
294     string ret;
295 
296     foreach(func; AllFunctions!Modules)
297     {
298         foreach(oc, overload; __traits(getOverloads, func.module_, func.name))
299         {
300             alias RT = ReturnType!overload;
301             alias ParamTypes = Parameters!overload;
302             alias Types = AliasSeq!(RT, ParamTypes);
303 
304             static if(Filter!(verifySupported, Types).length != Types.length)
305                 continue;
306             else
307             {
308                 addImports!Types(imports);
309 
310                 static foreach(nda; 0 .. numDefaultArgs!overload + 1)
311                 {{
312                     enum numParams = ParamTypes.length - nda;
313                     enum interfaceName = format("%s%s_%s", getDLangInterfaceName(func.moduleName, null, func.name), oc, numParams);
314                     alias returnTypeStr = getDLangInterfaceType!RT;
315                     alias paramNames = staticMap!(AdjParamName, ParameterIdentifierTuple!overload);
316 
317                     static if (!is(RT == void))
318                         string retType = mixin(interp!"returnValue!(${returnTypeStr})");
319                     else
320                         string retType = "returnVoid";
321 
322                     string funcStr = "extern(C) export ";
323                     funcStr ~= mixin(interp!"${retType} ${interfaceName}(");
324 
325                     static foreach(pc; 0 .. numParams)
326                         funcStr ~= mixin(interp!"${getDLangInterfaceType!(ParamTypes[pc])} ${paramNames[pc]}, ");
327 
328                     if(numParams != 0)
329                         funcStr = funcStr[0 .. $ - 2];
330 
331                     funcStr ~= ") nothrow {" ~ newline;
332                     funcStr ~= "    try {" ~ newline;
333                     funcStr ~= methodSetup ~ newline;
334                     funcStr ~= "        ";
335 
336                     if (!is(RT == void))
337                         funcStr ~= mixin(interp!"auto __return__ = ${func.name}(");
338                     else
339                         funcStr ~= mixin(interp!"${func.name}(");
340 
341                     static foreach(pc; 0 .. numParams)
342                         funcStr ~= mixin(interp!"${generateParameter!(ParamTypes[pc])(paramNames[pc])}, ");
343 
344                     if(numParams != 0)
345                         funcStr = funcStr[0 .. $ - 2];
346 
347                     funcStr ~= ");" ~ newline;
348 
349                     if (!is(RT == void))
350                         funcStr ~= mixin(interp!"        return ${retType}(${generateReturn!RT(\"__return__\")});${newline}");
351                     else
352                         funcStr ~= mixin(interp!"        return ${retType}();${newline}");
353 
354                     funcStr ~= "    } catch (Exception __ex__) {" ~ newline;
355                     funcStr ~= mixin(interp!"        return ${retType}(__ex__);${newline}");
356                     funcStr ~= "    }" ~ newline;
357                     funcStr ~= "}" ~ newline;
358 
359                     ret ~= funcStr;
360                 }}
361             }
362         }
363     }
364 
365     return ret;
366 }
367 
368 private string generateSliceMethods(T)(ref string[] imports) {
369     import autowrap.csharp.common : getDLangSliceInterfaceName;
370 
371     import std.traits : fullyQualifiedName, moduleName,  TemplateOf;
372 
373     addImport!T(imports);
374 
375     alias fqn = getDLangInterfaceType!T;
376 
377     //Generate slice creation method
378     string ret = mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Create\")}(size_t capacity) nothrow {${newline}");
379     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}[])"));
380     ret ~= "}" ~ newline;
381 
382     //Generate slice method
383     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Slice\")}(${fqn}[] slice, size_t begin, size_t end) nothrow {${newline}");
384     ret ~= generateMethodErrorHandling(mixin(interp!"        ${fqn}[] __temp__ = slice[begin..end];${newline}        pinPointer(cast(void*)__temp__.ptr);${newline}        return returnValue!(${fqn}[])(__temp__);"), mixin(interp!"returnValue!(${fqn}[])"));
385     ret ~= "}" ~ newline;
386 
387     //Generate get method
388     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}) ${getDLangSliceInterfaceName(fqn, \"Get\")}(${fqn}[] slice, size_t index) nothrow {${newline}");
389     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn})(slice[index]);"), mixin(interp!"returnValue!(${fqn})"));
390     ret ~= "}" ~ newline;
391 
392     //Generate set method
393     ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangSliceInterfaceName(fqn, \"Set\")}(${fqn}[] slice, size_t index, ${fqn} set) nothrow {${newline}");
394     ret ~= generateMethodErrorHandling(mixin(interp!"        slice[index] = set;${newline}        return returnVoid();"), "returnVoid");
395     ret ~= "}" ~ newline;
396 
397     //Generate item append method
398     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendValue\")}(${fqn}[] slice, ${fqn} append) nothrow {${newline}");
399     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
400     ret ~= "}" ~ newline;
401 
402     //Generate slice append method
403     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendSlice\")}(${fqn}[] slice, ${fqn}[] append) nothrow {${newline}");
404     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
405     ret ~= "}" ~ newline;
406 
407     return ret;
408 }
409 
410 private string generateMethodErrorHandling(string insideCode, string returnType) {
411     string ret = "    try {" ~ newline;
412     ret ~= methodSetup ~ newline;
413     ret ~= insideCode ~ newline;
414     ret ~= "    } catch (Exception __ex__) {" ~ newline;
415     ret ~= mixin(interp!"        return ${returnType}(__ex__);${newline}");
416     ret ~= "    }" ~ newline;
417     return ret;
418 }
419 
420 private string generateParameter(T)(string name) {
421     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
422     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
423     import std.traits : fullyQualifiedName;
424 
425     alias fqn = fullyQualifiedName!(PrimordialType!T);
426     static if (isDateTimeType!T) {
427         return mixin(interp!"fromDatetime!(${fqn})(${name})");
428     } else static if (isDateTimeArrayType!T) {
429         return mixin(interp!"fromDatetime1DArray!(${fqn})(${name})");
430     } else {
431         return name;
432     }
433 }
434 
435 private string generateReturn(T)(string name) {
436     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
437     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
438     import std.traits : fullyQualifiedName;
439 
440     alias fqn = fullyQualifiedName!(PrimordialType!T);
441     static if (isDateTimeType!T) {
442         return mixin(interp!"toDatetime!(${fqn})(${name})");
443     } else static if (isDateTimeArrayType!T) {
444         return mixin(interp!"toDatetime1DArray!(${fqn})(${name})");
445     } else {
446         return name;
447     }
448 }
449 
450 private void addImports(T...)(ref string[] imports)
451 {
452     foreach(U; T)
453         addImport!U(imports);
454 }
455 
456 private void addImport(T)(ref string[] imports)
457 {
458     import std.algorithm.searching : canFind;
459     import std.traits : isBuiltinType, isDynamicArray, moduleName;
460     import autowrap.csharp.common : isSupportedType;
461 
462     static assert(isSupportedType!T, "missing check for supported type");
463 
464     static if(isDynamicArray!T)
465         addImport!(ElementType!T)(imports);
466     else static if(!isBuiltinType!T)
467     {
468         enum mod = moduleName!T;
469         if(!mod.empty && !imports.canFind(mod))
470             imports ~= mod;
471     }
472 }
473 
474 private string getDLangInterfaceType(T)() {
475     import autowrap.csharp.common : isDateTimeArrayType, isDateTimeType;
476     import std.traits : fullyQualifiedName;
477     if (isDateTimeType!T) {
478         return "datetime";
479     } else if (isDateTimeArrayType!T) {
480         return "datetime[]";
481     } else {
482         return fullyQualifiedName!T;
483     }
484 }