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