1 /**
2    Function translations.
3  */
4 module dpp.translation.function_;
5 
6 import dpp.from;
7 
8 
9 enum OPERATOR_PREFIX = "operator";
10 
11 
12 string[] translateFunction(in from!"clang".Cursor cursor,
13                            ref from!"dpp.runtime.context".Context context)
14     @safe
15 {
16     import dpp.translation.dlang: maybeRename, maybePragma;
17     import dpp.translation.aggregate: maybeRememberStructs;
18     import dpp.translation.type: translate;
19     import clang: Cursor, Type, Language;
20     import std.array: join, array;
21     import std.conv: text;
22     import std.algorithm: any, endsWith;
23     import std.typecons: Yes;
24 
25     assert(
26         cursor.kind == Cursor.Kind.FunctionDecl ||
27         cursor.kind == Cursor.Kind.CXXMethod ||
28         cursor.kind == Cursor.Kind.Constructor ||
29         cursor.kind == Cursor.Kind.Destructor ||
30         cursor.kind == Cursor.Kind.ConversionFunction
31     );
32 
33     // FIXME - stop special casing the move ctor
34     auto moveCtorLines = maybeMoveCtor(cursor, context);
35     if(moveCtorLines) return moveCtorLines;
36 
37     string[] lines;
38 
39     lines ~= maybeCopyCtor(cursor, context);
40     lines ~= maybeOperator(cursor, context);
41 
42     maybeRememberStructs(paramTypes(cursor), context);
43 
44     const spelling = functionSpelling(cursor, context);
45 
46     lines ~= [
47         maybePragma(cursor, context) ~ functionDecl(cursor, context, spelling)
48     ];
49 
50 
51     context.log("");
52 
53     return lines;
54 }
55 
56 private string functionDecl(
57     in from!"clang".Cursor cursor,
58     ref from!"dpp.runtime.context".Context context,
59     in string spelling,
60     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names
61 )
62     @safe
63 {
64     import std.conv: text;
65     import std.algorithm: endsWith;
66     import std.array: join;
67 
68     context.log("Function return type (raw):        ", cursor.type.returnType);
69     const returnType = returnType(cursor, context);
70     context.log("Function return type (translated): ", returnType);
71 
72     const params = translateAllParamTypes(cursor, context, names).join(", ");
73     context.log("params: ", params);
74     // const C++ method?
75     const const_ = cursor.isConstCppMethod ? " const" : "";
76 
77     return text(returnType, " ", spelling, "(", params, ") @nogc nothrow", const_, ";");
78 }
79 
80 private string returnType(in from!"clang".Cursor cursor,
81                           ref from!"dpp.runtime.context".Context context)
82     @safe
83 {
84     import dpp.translation.type: translate;
85     import clang: Cursor;
86     import std.typecons: Yes;
87 
88     const indentation = context.indentation;
89 
90     const dType = cursor.kind == Cursor.Kind.Constructor || cursor.kind == Cursor.Kind.Destructor
91         ? ""
92         : translate(cursor.returnType, context, Yes.translatingFunction);
93 
94     context.setIndentation(indentation);
95 
96     const static_ = cursor.storageClass == Cursor.StorageClass.Static ? "static " : "";
97 
98     return static_ ~ dType;
99 }
100 
101 private string[] maybeOperator(in from!"clang".Cursor cursor,
102                                ref from!"dpp.runtime.context".Context context)
103     @safe
104 {
105     import std.algorithm: map;
106     import std.array: join;
107     import std.typecons: Yes;
108     import std.range: iota;
109     import std.conv: text;
110 
111     if(!isSupportedOperatorInD(cursor)) return [];
112 
113     const params = translateAllParamTypes(cursor, context);
114 
115     return [
116         // remove semicolon from the end with [0..$-1]
117         `extern(D) ` ~ functionDecl(cursor, context, operatorSpellingD(cursor, context), Yes.names)[0..$-1],
118         `{`,
119         `    return ` ~ operatorSpellingCpp(cursor, context) ~ `(` ~ params.length.iota.map!(a => text("arg", a)).join(", ") ~ `);`,
120         `}`,
121     ];
122 }
123 
124 private bool isSupportedOperatorInD(in from!"clang".Cursor cursor) @safe nothrow {
125     import clang: Cursor;
126     import std.algorithm: map, canFind;
127 
128     if(!isOperator(cursor)) return false;
129     // No D support for free function operator overloads
130     if(cursor.semanticParent.kind == Cursor.Kind.TranslationUnit) return false;
131 
132     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
133     const unsupportedSpellings = [`!`, `,`, `&&`, `||`, `->`, `->*`];
134     if(unsupportedSpellings.canFind(cppOperator)) return false;
135 
136     if(isUnaryOperator(cursor) && cppOperator == "&") return false;
137 
138      return true;
139 }
140 
141 private bool isOperator(in from!"clang".Cursor cursor) @safe pure nothrow {
142     import std.algorithm: startsWith;
143     return cursor.spelling.startsWith(OPERATOR_PREFIX);
144 }
145 
146 
147 private string functionSpelling(in from!"clang".Cursor cursor,
148                                 ref from!"dpp.runtime.context".Context context)
149     @safe
150 {
151     import clang: Cursor;
152     import std.algorithm: startsWith;
153 
154 
155     if(cursor.kind == Cursor.Kind.Constructor) return "this";
156     if(cursor.kind == Cursor.Kind.Destructor) return "~this";
157 
158     if(cursor.spelling.startsWith(OPERATOR_PREFIX)) return operatorSpellingCpp(cursor, context);
159 
160     // if no special case
161     return context.rememberLinkable(cursor);
162 }
163 
164 private string operatorSpellingD(in from!"clang".Cursor cursor,
165                                  ref from!"dpp.runtime.context".Context context)
166     @safe
167 {
168     import clang: Cursor;
169     import std.range: walkLength;
170     import std.algorithm: canFind;
171 
172     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
173 
174     if(cursor.kind == Cursor.Kind.ConversionFunction) {
175         return `opCast(T: ` ~ returnType(cursor, context) ~ `)`;
176     }
177 
178     if(cppOperator.length > 1 &&
179        cppOperator[$-1] == '=' &&
180        (cppOperator.length != 2 || !['=', '!', '<', '>'].canFind(cppOperator[0])))
181         return `opOpAssign(string op: "` ~ cppOperator[0 .. $-1] ~ `")`;
182 
183     assert(isUnaryOperator(cursor) || isBinaryOperator(cursor));
184     const dFunction = isBinaryOperator(cursor) ? "opBinary" : "opUnary";
185 
186     switch(cppOperator) {
187         default: return dFunction ~ `(string op: "` ~ cppOperator ~ `")`;
188         case "=": return `opAssign`;
189         case "()": return `opCall`;
190         case "[]": return `opIndex`;
191         case "==": return `opEquals`;
192     }
193 }
194 
195 private bool isUnaryOperator(in from!"clang".Cursor cursor) @safe nothrow {
196     import std.range: walkLength;
197     return isOperator(cursor) && paramTypes(cursor).walkLength == 0;
198 }
199 
200 private bool isBinaryOperator(in from!"clang".Cursor cursor) @safe nothrow {
201     import std.range: walkLength;
202     return isOperator(cursor) && paramTypes(cursor).walkLength == 1;
203 }
204 
205 
206 private string operatorSpellingCpp(in from!"clang".Cursor cursor,
207                                    ref from!"dpp.runtime.context".Context context)
208     @safe
209 {
210     import dpp.translation.type: translate;
211     import clang: Cursor;
212     import std..string: replace;
213 
214     const operator = cursor.spelling[OPERATOR_PREFIX.length .. $];
215 
216     if(cursor.kind == Cursor.Kind.ConversionFunction) {
217         return "opCppCast_" ~ translate(cursor.returnType, context).replace(".", "_");
218     }
219 
220     switch(operator) {
221         default: throw new Exception("Unknown C++ spelling for operator '" ~ operator ~ "'");
222         case   "+":  return `opCppPlus`;
223         case   "-":  return `opCppMinus`;
224         case  "++":  return `opCppIncrement`;
225         case  "--":  return `opCppDecrement`;
226         case   "*":  return `opCppMul`;
227         case   "/":  return `opCppDiv`;
228         case   "&":  return `opCppAmpersand`;
229         case   "~":  return `opCppTilde`;
230         case   "%":  return `opCppMod`;
231         case   "^":  return `opCppCaret`;
232         case   "|":  return `opCppPipe`;
233         case   "=":  return `opCppAssign`;
234         case  ">>":  return `opCppRShift`;
235         case  "<<":  return `opCppLShift`;
236         case  "->":  return `opCppArrow`;
237         case   "!":  return `opCppBang`;
238         case  "&&":  return `opCppAnd`;
239         case  "||":  return `opCppOr`;
240         case   ",":  return `opCppComma`;
241         case "->*":  return `opCppArrowStar`;
242         case  "+=":  return `opCppPlusAssign`;
243         case  "-=":  return `opCppMinusAssign`;
244         case  "*=":  return `opCppMulAssign`;
245         case  "/=":  return `opCppDivAssign`;
246         case  "%=":  return `opCppModAssign`;
247         case  "^=":  return `opCppCaretAssign`;
248         case  "&=":  return `opCppAmpersandAssign`;
249         case  "|=":  return `opCppPipeAssign`;
250         case ">>=":  return `opCppRShiftAssign`;
251         case "<<=":  return `opCppLShiftAssign`;
252         case "()":   return `opCppCall`;
253         case "[]":   return `opCppIndex`;
254         case "==":   return `opCppEquals`;
255         case "!=":   return `opCppNotEquals`;
256         case "<=":   return `opCppLessEquals`;
257         case ">=":   return `opCppMoreEquals`;
258         case  "<":   return `opCppLess`;
259         case  ">":   return `opCppMore`;
260         case " new": return `opCppNew`;
261         case " new[]": return `opCppNewArray`;
262         case " delete": return `opCppDelete`;
263         case " delete[]": return `opCppDeleteArray`;
264     }
265 
266     assert(0);
267 }
268 
269 
270 // Add a non-const ref that forwards to the const ref copy ctor
271 // so that lvalues don't match the by-value ctor
272 private string[] maybeCopyCtor(in from!"clang".Cursor cursor,
273                                ref from!"dpp.runtime.context".Context context)
274     @safe
275 {
276 
277     import dpp.translation.dlang: maybeRename, maybePragma;
278     import dpp.translation.type: translate;
279     import clang: Cursor, Type;
280 
281     if(!cursor.isCopyConstructor) return [];
282 
283     const paramType = () @trusted {  return paramTypes(cursor).front; }();
284     const pointee = translate(paramType.pointee, context);
285     const dType = pointee["const(".length .. $ - 1]; // remove the constness
286 
287     return [
288         `this(ref ` ~ dType ~ ` other)`,
289         `{`,
290         `   this(*cast(const ` ~ dType ~ `*) &other);`,
291         `}`,
292     ];
293 }
294 
295 private string[] maybeMoveCtor(in from!"clang".Cursor cursor,
296                                ref from!"dpp.runtime.context".Context context)
297     @safe
298 {
299 
300     import dpp.translation.dlang: maybeRename, maybePragma;
301     import dpp.translation.type: translate;
302     import clang: Cursor, Type;
303 
304     if(!cursor.isMoveConstructor) return [];
305 
306     const paramType = () @trusted {  return paramTypes(cursor).front; }();
307     const pointee = translate(paramType.pointee, context);
308 
309     return [
310         maybePragma(cursor, context) ~ " this(" ~ pointee ~ "*);",
311         "this(" ~ translate(paramType, context) ~ " wrapper) {",
312         "    this(wrapper.ptr);",
313         "    *wrapper.ptr = typeof(*wrapper.ptr).init;",
314         "}",
315         "this(" ~ pointee ~ " other)",
316         "{",
317         "    this(&other);",
318         "}",
319     ];
320 }
321 
322 // includes variadic params
323 private auto translateAllParamTypes(
324     in from!"clang".Cursor cursor,
325     ref from!"dpp.runtime.context".Context context,
326     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names,
327 )
328     @safe
329 {
330     import std.algorithm: endsWith, map;
331     import std.array: array;
332     import std.range: enumerate;
333     import std.conv: text;
334 
335     // Here we used to check that if there were no parameters and the language is C,
336     // then the correct translation in D would be (...);
337     // However, that's not allowed in D. It just so happens that C production code
338     // exists that doesn't bother with (void), so instead of producing something that
339     // doesn't compile, we compromise and assume the user meant (void)
340 
341     const paramTypes = translateParamTypes(cursor, context).array;
342     const isVariadic = cursor.type.spelling.endsWith("...)");
343     const variadicParams = isVariadic ? ["..."] : [];
344 
345     return enumerate(paramTypes ~ variadicParams)
346         .map!(a => names ? a[1] ~ text(" arg", a[0]) : a[1])
347         .array;
348 }
349 
350 auto translateParamTypes(in from!"clang".Cursor cursor,
351                          ref from!"dpp.runtime.context".Context context)
352     @safe
353 {
354     import dpp.translation.type: translate;
355     import clang: Type, Language;
356     import std.algorithm: map;
357     import std.range: tee;
358     import std.typecons: Yes;
359 
360     // See #43
361     const(Type) deunexpose(in Type type) {
362         return type.kind == Type.Kind.Unexposed && cursor.language != Language.CPlusPlus
363             ? type.canonical
364             : type;
365     }
366 
367     return paramTypes(cursor)
368         .tee!((a){ context.log("    Function Child: ", a, "  canonical ", a.canonical); })
369         .map!(a => translate(deunexpose(a), context, Yes.translatingFunction))
370         ;
371 }
372 
373 private auto paramTypes(in from!"clang".Cursor cursor)
374     @safe
375 {
376     import clang: Cursor;
377     import std.algorithm: map, filter;
378 
379     return cursor
380         .children
381         .filter!(a => a.kind == Cursor.Kind.ParmDecl)
382         .map!(a => a.type)
383         ;
384 }