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;
20     import std.array: join, array;
21     import std.conv: text;
22     import std.algorithm: any, endsWith, canFind;
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     if(ignoreFunction(cursor)) return [];
34 
35     // FIXME - stop special casing the move ctor
36     auto moveCtorLines = maybeMoveCtor(cursor, context);
37     if(moveCtorLines) return moveCtorLines;
38 
39     string[] lines;
40 
41     lines ~= maybeCopyCtor(cursor, context);
42     lines ~= maybeOperator(cursor, context);
43 
44     maybeRememberStructs(paramTypes(cursor), context);
45 
46     const spelling = functionSpelling(cursor, context);
47 
48     lines ~= [
49         maybePragma(cursor, context) ~ functionDecl(cursor, context, spelling)
50     ];
51 
52     context.log("");
53 
54     return lines;
55 }
56 
57 private bool ignoreFunction(in from!"clang".Cursor cursor) @safe {
58     import clang: Cursor, Type, Token;
59     import std.algorithm: canFind;
60 
61     // C++ partial specialisation function bodies
62     if(cursor.semanticParent.kind == Cursor.Kind.ClassTemplatePartialSpecialization &&
63        cursor.semanticParent.type.kind == Type.Kind.Unexposed)
64         return true;
65 
66     // FIXME
67     if(cursor.semanticParent.kind == Cursor.Kind.ClassTemplate &&
68        cursor.semanticParent.spelling == "vector")
69         return true;
70 
71 
72     // C++ deleted functions
73     if(cursor.tokens.canFind(Token(Token.Kind.Keyword, "delete"))) return true;
74 
75     // FIXME - no default contructors for structs in D
76     // We're not even checking if it's a struct here, so classes are being
77     // affected for no reason.
78     if(cursor.kind == Cursor.Kind.Constructor && numParams(cursor) == 0) return true;
79 
80     return false;
81 }
82 
83 private string functionDecl(
84     in from!"clang".Cursor cursor,
85     ref from!"dpp.runtime.context".Context context,
86     in string spelling,
87     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names
88 )
89     @safe
90 {
91     import std.conv: text;
92     import std.algorithm: endsWith;
93     import std.array: join;
94 
95     context.log("Function return type (raw):        ", cursor.type.returnType);
96     const returnType = returnType(cursor, context);
97     context.log("Function return type (translated): ", returnType);
98 
99     const params = translateAllParamTypes(cursor, context, names).join(", ");
100     context.log("Translated parameters: '", params, "'");
101     // const C++ method?
102     const const_ = cursor.isConstCppMethod ? " const" : "";
103 
104     return text(returnType, " ", spelling, "(", params, ") @nogc nothrow", const_, ";");
105 }
106 
107 private string returnType(in from!"clang".Cursor cursor,
108                           ref from!"dpp.runtime.context".Context context)
109     @safe
110 {
111     import dpp.translation.type: translate;
112     import clang: Cursor;
113     import std.typecons: Yes;
114 
115     const indentation = context.indentation;
116 
117     const dType = cursor.kind == Cursor.Kind.Constructor || cursor.kind == Cursor.Kind.Destructor
118         ? ""
119         : translate(cursor.returnType, context, Yes.translatingFunction);
120 
121     context.setIndentation(indentation);
122 
123     const maybeStatic = cursor.storageClass == Cursor.StorageClass.Static ? "static " : "";
124 
125     return maybeStatic ~ dType;
126 }
127 
128 private string[] maybeOperator(in from!"clang".Cursor cursor,
129                                ref from!"dpp.runtime.context".Context context)
130     @safe
131 {
132     import std.algorithm: map;
133     import std.array: join;
134     import std.typecons: Yes;
135     import std.range: iota;
136     import std.conv: text;
137 
138     if(!isSupportedOperatorInD(cursor)) return [];
139 
140     const params = translateAllParamTypes(cursor, context);
141 
142     return [
143         // remove semicolon from the end with [0..$-1]
144         `extern(D) ` ~ functionDecl(cursor, context, operatorSpellingD(cursor, context), Yes.names)[0..$-1],
145         `{`,
146         `    return ` ~ operatorSpellingCpp(cursor, context) ~ `(` ~ params.length.iota.map!(a => text("arg", a)).join(", ") ~ `);`,
147         `}`,
148     ];
149 }
150 
151 private bool isSupportedOperatorInD(in from!"clang".Cursor cursor) @safe nothrow {
152     import clang: Cursor;
153     import std.algorithm: map, canFind;
154 
155     if(!isOperator(cursor)) return false;
156     // No D support for free function operator overloads
157     if(cursor.semanticParent.kind == Cursor.Kind.TranslationUnit) return false;
158 
159     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
160     const unsupportedSpellings = [`!`, `,`, `&&`, `||`, `->`, `->*`];
161     if(unsupportedSpellings.canFind(cppOperator)) return false;
162 
163     if(isUnaryOperator(cursor) && cppOperator == "&") return false;
164     if(!isUnaryOperator(cursor) && !isBinaryOperator(cursor)) return false;
165 
166      return true;
167 }
168 
169 private bool isOperator(in from!"clang".Cursor cursor) @safe pure nothrow {
170     import std.algorithm: startsWith;
171     return cursor.spelling.startsWith(OPERATOR_PREFIX);
172 }
173 
174 
175 private string functionSpelling(in from!"clang".Cursor cursor,
176                                 ref from!"dpp.runtime.context".Context context)
177     @safe
178 {
179     import clang: Cursor;
180     import std.algorithm: startsWith;
181 
182 
183     if(cursor.kind == Cursor.Kind.Constructor) return "this";
184     if(cursor.kind == Cursor.Kind.Destructor) return "~this";
185 
186     if(cursor.spelling.startsWith(OPERATOR_PREFIX)) return operatorSpellingCpp(cursor, context);
187 
188     // if no special case
189     return context.rememberLinkable(cursor);
190 }
191 
192 private string operatorSpellingD(in from!"clang".Cursor cursor,
193                                  ref from!"dpp.runtime.context".Context context)
194     @safe
195 {
196     import clang: Cursor;
197     import std.range: walkLength;
198     import std.algorithm: canFind;
199     import std.conv: text;
200 
201     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
202 
203     if(cursor.kind == Cursor.Kind.ConversionFunction) {
204         return `opCast(T: ` ~ returnType(cursor, context) ~ `)`;
205     }
206 
207     if(cppOperator.length > 1 &&
208        cppOperator[$-1] == '=' &&
209        (cppOperator.length != 2 || !['=', '!', '<', '>'].canFind(cppOperator[0])))
210         return `opOpAssign(string op: "` ~ cppOperator[0 .. $-1] ~ `")`;
211 
212     assert(isUnaryOperator(cursor) || isBinaryOperator(cursor),
213            text("Cursor is neither a unary or binary operator: ", cursor, "@", cursor.sourceRange.start));
214     const dFunction = isBinaryOperator(cursor) ? "opBinary" : "opUnary";
215 
216     // Some of the operators here have empty parentheses around them. This is to
217     // to make them templates and only be instantiated if needed. See #102.
218     switch(cppOperator) {
219         default: return dFunction ~ `(string op: "` ~ cppOperator ~ `")`;
220         case "=": return `opAssign()`;
221         case "()": return `opCall()`;
222         case "[]": return `opIndex()`;
223         case "==": return `opEquals()`;
224     }
225 }
226 
227 private bool isUnaryOperator(in from!"clang".Cursor cursor) @safe nothrow {
228     return isOperator(cursor) && numParams(cursor) == 0;
229 }
230 
231 private bool isBinaryOperator(in from!"clang".Cursor cursor) @safe nothrow {
232     return isOperator(cursor) && numParams(cursor) == 1;
233 }
234 
235 private long numParams(in from!"clang".Cursor cursor) @safe nothrow {
236     import std.range: walkLength;
237     return paramTypes(cursor).walkLength;
238 }
239 
240 private string operatorSpellingCpp(in from!"clang".Cursor cursor,
241                                    ref from!"dpp.runtime.context".Context context)
242     @safe
243 {
244     import dpp.translation.type: translate;
245     import dpp.translation.exception: UntranslatableException;
246     import clang: Cursor;
247     import std..string: replace;
248     import std.algorithm: startsWith;
249 
250     const operator = cursor.spelling[OPERATOR_PREFIX.length .. $];
251 
252     if(cursor.kind == Cursor.Kind.ConversionFunction) {
253         return "opCppCast_" ~ translate(cursor.returnType, context).replace(".", "_");
254     }
255 
256     switch(operator) {
257         default:
258             if(operator.startsWith(`""`))  // user-defined string literal
259                 throw new UntranslatableException("Cannot translate user-defined literals");
260 
261             throw new Exception("Unknown C++ spelling for operator '" ~ operator ~ "'");
262 
263         case        "+":  return `opCppPlus`;
264         case        "-":  return `opCppMinus`;
265         case       "++":  return `opCppIncrement`;
266         case       "--":  return `opCppDecrement`;
267         case        "*":  return `opCppMul`;
268         case        "/":  return `opCppDiv`;
269         case        "&":  return `opCppAmpersand`;
270         case        "~":  return `opCppTilde`;
271         case        "%":  return `opCppMod`;
272         case        "^":  return `opCppCaret`;
273         case        "|":  return `opCppPipe`;
274         case        "=":  return `opCppAssign`;
275         case       ">>":  return `opCppRShift`;
276         case       "<<":  return `opCppLShift`;
277         case       "->":  return `opCppArrow`;
278         case        "!":  return `opCppBang`;
279         case       "&&":  return `opCppAnd`;
280         case       "||":  return `opCppOr`;
281         case        ",":  return `opCppComma`;
282         case      "->*":  return `opCppArrowStar`;
283         case       "+=":  return `opCppPlusAssign`;
284         case       "-=":  return `opCppMinusAssign`;
285         case       "*=":  return `opCppMulAssign`;
286         case       "/=":  return `opCppDivAssign`;
287         case       "%=":  return `opCppModAssign`;
288         case       "^=":  return `opCppCaretAssign`;
289         case       "&=":  return `opCppAmpersandAssign`;
290         case       "|=":  return `opCppPipeAssign`;
291         case      ">>=":  return `opCppRShiftAssign`;
292         case      "<<=":  return `opCppLShiftAssign`;
293         case       "()":  return `opCppCall`;
294         case       "[]":  return `opCppIndex`;
295         case       "==":  return `opCppEquals`;
296         case       "!=":  return `opCppNotEquals`;
297         case       "<=":  return `opCppLessEquals`;
298         case       ">=":  return `opCppMoreEquals`;
299         case        "<":  return `opCppLess`;
300         case        ">":  return `opCppMore`;
301         case      " new": return `opCppNew`;
302         case    " new[]": return `opCppNewArray`;
303         case   " delete": return `opCppDelete`;
304         case " delete[]": return `opCppDeleteArray`;
305     }
306 
307     assert(0);
308 }
309 
310 
311 // Add a non-const ref that forwards to the const ref copy ctor
312 // so that lvalues don't match the by-value ctor
313 private string[] maybeCopyCtor(in from!"clang".Cursor cursor,
314                                ref from!"dpp.runtime.context".Context context)
315     @safe
316 {
317 
318     import dpp.translation.dlang: maybeRename, maybePragma;
319     import dpp.translation.type: translate;
320     import clang: Cursor, Type;
321 
322     if(!cursor.isCopyConstructor) return [];
323 
324     const param = params(cursor).front;
325     const translated = translateFunctionParam(cursor, param, context);
326     const dType = translated["ref const(".length .. $ - 1];  // remove the constness
327 
328     return [
329         `this(ref ` ~ dType ~ ` other)`,
330         `{`,
331         `   this(*cast(const ` ~ dType ~ `*) &other);`,
332         `}`,
333     ];
334 }
335 
336 
337 private string[] maybeMoveCtor(in from!"clang".Cursor cursor,
338                                ref from!"dpp.runtime.context".Context context)
339     @safe
340 {
341 
342     import dpp.translation.dlang: maybeRename, maybePragma;
343     import dpp.translation.type: translate;
344     import clang: Cursor, Type;
345 
346     if(!cursor.isMoveConstructor) return [];
347 
348     const paramType = () @trusted {  return paramTypes(cursor).front; }();
349     const pointee = translate(paramType.pointee, context);
350 
351     return [
352         maybePragma(cursor, context) ~ " this(" ~ pointee ~ "*);",
353         "this(" ~ translate(paramType, context) ~ " wrapper) {",
354         "    this(wrapper.ptr);",
355         "    *wrapper.ptr = typeof(*wrapper.ptr).init;",
356         "}",
357         "this(" ~ pointee ~ " other)",
358         "{",
359         "    this(&other);",
360         "}",
361     ];
362 }
363 
364 // includes variadic params
365 private auto translateAllParamTypes(
366     in from!"clang".Cursor cursor,
367     ref from!"dpp.runtime.context".Context context,
368     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names,
369 )
370     @safe
371 {
372     import std.algorithm: endsWith, map;
373     import std.array: array;
374     import std.range: enumerate;
375     import std.conv: text;
376 
377     // Here we used to check that if there were no parameters and the language is C,
378     // then the correct translation in D would be (...);
379     // However, that's not allowed in D. It just so happens that C production code
380     // exists that doesn't bother with (void), so instead of producing something that
381     // doesn't compile, we compromise and assume the user meant (void)
382 
383     const paramTypes = translateParamTypes(cursor, context).array;
384     const isVariadic = cursor.type.spelling.endsWith("...)");
385     const variadicParams = isVariadic ? ["..."] : [];
386 
387     return enumerate(paramTypes ~ variadicParams)
388         .map!(a => names ? a[1] ~ text(" arg", a[0]) : a[1])
389         .array;
390 }
391 
392 auto translateParamTypes(in from!"clang".Cursor cursor,
393                          ref from!"dpp.runtime.context".Context context)
394     @safe
395 {
396     import std.algorithm: map;
397     import std.range: tee, enumerate;
398 
399     return params(cursor)
400         .enumerate
401         .tee!((a) { context.log("    Function param #", a[0], " type: ", a[1].type, "  canonical ", a[1].type.canonical); })
402         .map!(t => translateFunctionParam(cursor, t[1], context))
403         ;
404 }
405 
406 
407 // translate a ParmDecl
408 private string translateFunctionParam(in from!"clang".Cursor function_,
409                                       in from!"clang".Cursor param,
410                                       ref from!"dpp.runtime.context".Context context)
411     @safe
412 {
413 
414     import dpp.translation.type: translate;
415     import clang: Type, Language;
416     import std.typecons: Yes;
417     import std.array: replace;
418 
419     // See #43
420     const(Type) deunexpose(in Type type) {
421         return type.kind == Type.Kind.Unexposed && function_.language != Language.CPlusPlus
422             ? type.canonical
423             : type;
424     }
425 
426     // See contract.ctor.copy.definition.declartion for libclang silliness leading to this.
427     // If the enclosing struct/class is templated and the function isn't, then we might
428     // get "type-parameter-0-0" spellings even when the actual name is e.g. `T`.
429     const numAggTemplateParams = function_.semanticParent.templateParams.length;
430     const numFunTemplateParams = function_.templateParams.length;
431 
432     // HACK
433     // FIXME: not sure what to do if the numbers aren't exactly 1 and 0
434     const useAggTemplateParamSpelling = numAggTemplateParams == 1 && numFunTemplateParams == 0;
435     const aggTemplateParamSpelling = useAggTemplateParamSpelling
436         ? function_.semanticParent.templateParams[0].spelling
437         : "";
438     const translation = translate(deunexpose(param.type), context, Yes.translatingFunction);
439 
440     return useAggTemplateParamSpelling
441         ? translation.replace("type_parameter_0_0", aggTemplateParamSpelling)
442         : translation;
443 }
444 
445 private auto paramTypes(in from!"clang".Cursor cursor) @safe {
446     import std.algorithm: map;
447     return params(cursor).map!(a => a.type) ;
448 }
449 
450 private auto params(in from!"clang".Cursor cursor) @safe {
451     import clang: Cursor;
452     import std.algorithm: filter;
453 
454     return cursor
455         .children
456         .filter!(a => a.kind == Cursor.Kind.ParmDecl)
457         ;
458 }