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     import std.algorithm: among;
254 
255     string ret;
256     alias fqn = getDLangInterfaceType!T;
257     if (is(T == class) || is(T == interface))
258     {
259         alias FieldTypes = Fields!T;
260         alias fieldNames = FieldNameTuple!T;
261         static foreach(fc; 0 .. FieldTypes.length)
262         {{
263             alias FT = FieldTypes[fc];
264             static if(verifySupported!FT && __traits(getProtection, __traits(getMember,T,fieldNames[fc])).among("export", "public"))
265             {
266                 alias fn = fieldNames[fc];
267                 static if (is(typeof(__traits(getMember, T, fn))))
268                 {
269                     addImport!FT(imports);
270 
271                     ret ~= mixin(interp!"extern(C) export returnValue!(${getDLangInterfaceType!FT}) ${getDLangInterfaceName(fqn, fn ~ \"_get\")}(${fqn} __obj__) nothrow {${newline}");
272                     ret ~= generateMethodErrorHandling(mixin(interp!"        auto __value__ = __obj__.${fn};${newline}        return returnValue!(${getDLangInterfaceType!FT})(${generateReturn!FT(\"__value__\")});"), mixin(interp!"returnValue!(${getDLangInterfaceType!FT})"));
273                     ret ~= "}" ~ newline;
274                     ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangInterfaceName(fqn, fn ~ \"_set\")}(${fqn} __obj__, ${getDLangInterfaceType!FT} value) nothrow {${newline}");
275                     ret ~= generateMethodErrorHandling(mixin(interp!"        __obj__.${fn} = ${generateParameter!FT(\"value\")};${newline}        return returnVoid();"), "returnVoid");
276                     ret ~= "}" ~ newline;
277                 }
278             }
279         }}
280     }
281     return ret;
282 }
283 
284 private string generateFunctions(Modules...)(ref string[] imports)
285     if(allSatisfy!(isModule, Modules))
286 {
287     import autowrap.csharp.common : getDLangInterfaceName, numDefaultArgs, verifySupported;
288     import autowrap.reflection: AllFunctions;
289 
290     import std.format : format;
291     import std.meta : AliasSeq, Filter, staticMap;
292     import std.traits : fullyQualifiedName, hasMember, functionAttributes, FunctionAttribute,
293                         ReturnType, Parameters, ParameterIdentifierTuple;
294 
295     string ret;
296 
297     foreach(func; AllFunctions!Modules)
298     {
299         foreach(oc, overload; __traits(getOverloads, func.module_, func.name))
300         {
301             alias RT = ReturnType!overload;
302             alias ParamTypes = Parameters!overload;
303             alias Types = AliasSeq!(RT, ParamTypes);
304 
305             static if(Filter!(verifySupported, Types).length != Types.length)
306                 continue;
307             else
308             {
309                 addImports!Types(imports);
310 
311                 static foreach(nda; 0 .. numDefaultArgs!overload + 1)
312                 {{
313                     enum numParams = ParamTypes.length - nda;
314                     enum interfaceName = format("%s%s_%s", getDLangInterfaceName(func.moduleName, null, func.name), oc, numParams);
315                     alias returnTypeStr = getDLangInterfaceType!RT;
316                     alias paramNames = staticMap!(AdjParamName, ParameterIdentifierTuple!overload);
317 
318                     static if (!is(RT == void))
319                         string retType = mixin(interp!"returnValue!(${returnTypeStr})");
320                     else
321                         string retType = "returnVoid";
322 
323                     string funcStr = "extern(C) export ";
324                     funcStr ~= mixin(interp!"${retType} ${interfaceName}(");
325 
326                     static foreach(pc; 0 .. numParams)
327                         funcStr ~= mixin(interp!"${getDLangInterfaceType!(ParamTypes[pc])} ${paramNames[pc]}, ");
328 
329                     if(numParams != 0)
330                         funcStr = funcStr[0 .. $ - 2];
331 
332                     funcStr ~= ") nothrow {" ~ newline;
333                     funcStr ~= "    try {" ~ newline;
334                     funcStr ~= methodSetup ~ newline;
335                     funcStr ~= "        ";
336 
337                     if (!is(RT == void))
338                         funcStr ~= mixin(interp!"auto __return__ = ${func.name}(");
339                     else
340                         funcStr ~= mixin(interp!"${func.name}(");
341 
342                     static foreach(pc; 0 .. numParams)
343                         funcStr ~= mixin(interp!"${generateParameter!(ParamTypes[pc])(paramNames[pc])}, ");
344 
345                     if(numParams != 0)
346                         funcStr = funcStr[0 .. $ - 2];
347 
348                     funcStr ~= ");" ~ newline;
349 
350                     if (!is(RT == void))
351                         funcStr ~= mixin(interp!"        return ${retType}(${generateReturn!RT(\"__return__\")});${newline}");
352                     else
353                         funcStr ~= mixin(interp!"        return ${retType}();${newline}");
354 
355                     funcStr ~= "    } catch (Exception __ex__) {" ~ newline;
356                     funcStr ~= mixin(interp!"        return ${retType}(__ex__);${newline}");
357                     funcStr ~= "    }" ~ newline;
358                     funcStr ~= "}" ~ newline;
359 
360                     ret ~= funcStr;
361                 }}
362             }
363         }
364     }
365 
366     return ret;
367 }
368 
369 private string generateSliceMethods(T)(ref string[] imports) {
370     import autowrap.csharp.common : getDLangSliceInterfaceName;
371 
372     import std.traits : fullyQualifiedName, moduleName,  TemplateOf;
373 
374     addImport!T(imports);
375 
376     alias fqn = getDLangInterfaceType!T;
377 
378     //Generate slice creation method
379     string ret = mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Create\")}(size_t capacity) nothrow {${newline}");
380     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}[])"));
381     ret ~= "}" ~ newline;
382 
383     //Generate slice method
384     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"Slice\")}(${fqn}[] slice, size_t begin, size_t end) nothrow {${newline}");
385     ret ~= generateMethodErrorHandling(mixin(interp!"        ${fqn}[] __temp__ = slice[begin..end];${newline}        pinPointer(cast(void*)__temp__.ptr);${newline}        return returnValue!(${fqn}[])(__temp__);"), mixin(interp!"returnValue!(${fqn}[])"));
386     ret ~= "}" ~ newline;
387 
388     //Generate get method
389     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}) ${getDLangSliceInterfaceName(fqn, \"Get\")}(${fqn}[] slice, size_t index) nothrow {${newline}");
390     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn})(slice[index]);"), mixin(interp!"returnValue!(${fqn})"));
391     ret ~= "}" ~ newline;
392 
393     //Generate set method
394     ret ~= mixin(interp!"extern(C) export returnVoid ${getDLangSliceInterfaceName(fqn, \"Set\")}(${fqn}[] slice, size_t index, ${fqn} set) nothrow {${newline}");
395     ret ~= generateMethodErrorHandling(mixin(interp!"        slice[index] = set;${newline}        return returnVoid();"), "returnVoid");
396     ret ~= "}" ~ newline;
397 
398     //Generate item append method
399     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendValue\")}(${fqn}[] slice, ${fqn} append) nothrow {${newline}");
400     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
401     ret ~= "}" ~ newline;
402 
403     //Generate slice append method
404     ret ~= mixin(interp!"extern(C) export returnValue!(${fqn}[]) ${getDLangSliceInterfaceName(fqn, \"AppendSlice\")}(${fqn}[] slice, ${fqn}[] append) nothrow {${newline}");
405     ret ~= generateMethodErrorHandling(mixin(interp!"        return returnValue!(${fqn}[])(slice ~= append);"), mixin(interp!"returnValue!(${fqn}[])"));
406     ret ~= "}" ~ newline;
407 
408     return ret;
409 }
410 
411 private string generateMethodErrorHandling(string insideCode, string returnType) {
412     string ret = "    try {" ~ newline;
413     ret ~= methodSetup ~ newline;
414     ret ~= insideCode ~ newline;
415     ret ~= "    } catch (Exception __ex__) {" ~ newline;
416     ret ~= mixin(interp!"        return ${returnType}(__ex__);${newline}");
417     ret ~= "    }" ~ newline;
418     return ret;
419 }
420 
421 private string generateParameter(T)(string name) {
422     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
423     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
424     import std.traits : fullyQualifiedName;
425 
426     alias fqn = fullyQualifiedName!(PrimordialType!T);
427     static if (isDateTimeType!T) {
428         return mixin(interp!"fromDatetime!(${fqn})(${name})");
429     } else static if (isDateTimeArrayType!T) {
430         return mixin(interp!"fromDatetime1DArray!(${fqn})(${name})");
431     } else {
432         return name;
433     }
434 }
435 
436 private string generateReturn(T)(string name) {
437     import autowrap.csharp.common : isDateTimeType, isDateTimeArrayType;
438     import std.datetime : DateTime, Date, TimeOfDay, SysTime, Duration;
439     import std.traits : fullyQualifiedName;
440 
441     alias fqn = fullyQualifiedName!(PrimordialType!T);
442     static if (isDateTimeType!T) {
443         return mixin(interp!"toDatetime!(${fqn})(${name})");
444     } else static if (isDateTimeArrayType!T) {
445         return mixin(interp!"toDatetime1DArray!(${fqn})(${name})");
446     } else {
447         return name;
448     }
449 }
450 
451 private void addImports(T...)(ref string[] imports)
452 {
453     foreach(U; T)
454         addImport!U(imports);
455 }
456 
457 private void addImport(T)(ref string[] imports)
458 {
459     import std.algorithm.searching : canFind;
460     import std.traits : isBuiltinType, isDynamicArray, moduleName;
461     import autowrap.csharp.common : isSupportedType;
462 
463     static assert(isSupportedType!T, "missing check for supported type");
464 
465     static if(isDynamicArray!T)
466         addImport!(ElementType!T)(imports);
467     else static if(!isBuiltinType!T)
468     {
469         enum mod = moduleName!T;
470         if(!mod.empty && !imports.canFind(mod))
471             imports ~= mod;
472     }
473 }
474 
475 private string getDLangInterfaceType(T)() {
476     import autowrap.csharp.common : isDateTimeArrayType, isDateTimeType;
477     import std.traits : fullyQualifiedName;
478     if (isDateTimeType!T) {
479         return "datetime";
480     } else if (isDateTimeArrayType!T) {
481         return "datetime[]";
482     } else {
483         return fullyQualifiedName!T;
484     }
485 }