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