1 /**
2    Translate aggregates
3  */
4 module dpp.translation.aggregate;
5 
6 import dpp.from;
7 import std.range: isInputRange;
8 
9 
10 enum MAX_BITFIELD_WIDTH = 64;
11 
12 
13 string[] translateStruct(in from!"clang".Cursor cursor,
14                          ref from!"dpp.runtime.context".Context context)
15     @safe
16 {
17     return translateStrass(cursor, context, "struct");
18 }
19 
20 string[] translateClass(in from!"clang".Cursor cursor,
21                         ref from!"dpp.runtime.context".Context context)
22     @safe
23 {
24     import clang: Token;
25     import std.algorithm: canFind;
26 
27     const cKeyword = cursor.tokens.canFind(Token(Token.Kind.Keyword, "class"))
28         ? "class"
29         : "struct";
30 
31     return translateStrass(cursor, context, cKeyword);
32 }
33 
34 // "strass" is a struct or class
35 private string[] translateStrass(in from!"clang".Cursor cursor,
36                                  ref from!"dpp.runtime.context".Context context,
37                                  in string cKeyword)
38     @safe
39     in(
40         cursor.kind == from!"clang".Cursor.Kind.StructDecl ||
41         cursor.kind == from!"clang".Cursor.Kind.ClassDecl ||
42         cursor.kind == from!"clang".Cursor.Kind.ClassTemplate ||
43         cursor.kind == from!"clang".Cursor.Kind.ClassTemplatePartialSpecialization
44     )
45   do
46 {
47     import dpp.translation.template_: templateSpelling, translateTemplateParams,
48         translateSpecialisedTemplateParams;
49     import clang: Cursor;
50     import std.typecons: Nullable, nullable;
51     import std.array: join;
52     import std.conv: text;
53 
54     Nullable!string spelling() {
55 
56         // full template
57         if(cursor.kind == Cursor.Kind.ClassTemplate)
58             return nullable(templateSpelling(cursor, translateTemplateParams(cursor, context)));
59 
60         // partial or full template specialisation
61         if(cursor.type.numTemplateArguments != -1)
62             return nullable(templateSpelling(cursor, translateSpecialisedTemplateParams(cursor, context)));
63 
64         // non-template class/struct
65         return Nullable!string();
66     }
67 
68     const dKeyword = "struct";
69 
70     return translateAggregate(context, cursor, cKeyword, dKeyword, spelling);
71 }
72 
73 
74 string[] translateUnion(in from!"clang".Cursor cursor,
75                         ref from!"dpp.runtime.context".Context context)
76     @safe
77     in(cursor.kind == from!"clang".Cursor.Kind.UnionDecl)
78     do
79 {
80     import clang: Cursor;
81     return translateAggregate(context, cursor, "union");
82 }
83 
84 
85 string[] translateEnum(in from!"clang".Cursor cursor,
86                        ref from!"dpp.runtime.context".Context context)
87     @safe
88     in(cursor.kind == from!"clang".Cursor.Kind.EnumDecl)
89     do
90 {
91     import dpp.translation.dlang: maybeRename;
92     import clang: Cursor, Token;
93     import std.typecons: nullable;
94     import std.algorithm: canFind;
95 
96     const enumName = context.spellingOrNickname(cursor);
97     string[] lines;
98 
99     const isEnumClass = cursor.tokens.canFind(Token(Token.Kind.Keyword, "class"));
100 
101     if(!isEnumClass && !context.options.alwaysScopedEnums) {
102         // Translate it twice so that C semantics are the same (global names)
103         // but also have a named version for optional type correctness and
104         // reflection capabilities.
105         // This means that `enum Foo { foo, bar }` in C will become:
106         // `enum Foo { foo, bar }` _and_
107         // `enum foo = Foo.foo; enum bar = Foo.bar;` in D.
108 
109         foreach(member; cursor) {
110             if(!member.isDefinition) continue;
111             auto memName = maybeRename(member, context);
112             lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`;
113         }
114     }
115 
116     return
117         translateAggregate(context, cursor, "enum", nullable(enumName)) ~
118         lines;
119 }
120 
121 // not pure due to Cursor.opApply not being pure
122 string[] translateAggregate(
123     ref from!"dpp.runtime.context".Context context,
124     in from!"clang".Cursor cursor,
125     in string keyword,
126     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
127 )
128     @safe
129 {
130     return translateAggregate(context, cursor, keyword, keyword, spelling);
131 }
132 
133 
134 // not pure due to Cursor.opApply not being pure
135 string[] translateAggregate(
136     ref from!"dpp.runtime.context".Context context,
137     in from!"clang".Cursor cursor,
138     in string cKeyword,
139     in string dKeyword,
140     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
141 )
142     @safe
143 {
144     import dpp.translation.translation: translate;
145     import clang: Cursor, Type, AccessSpecifier;
146     import std.algorithm: map;
147     import std.array: array;
148 
149     // remember all aggregate declarations
150     context.rememberAggregate(cursor);
151 
152     const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get;
153     const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record
154         ? "static " ~ dKeyword
155         : dKeyword;
156     const firstLine = realDlangKeyword ~ ` ` ~ name;
157 
158     if(!cursor.isDefinition) return [firstLine ~ `;`];
159 
160     string[] lines;
161     lines ~= firstLine;
162     lines ~= `{`;
163 
164     // In C++ classes have private access as default
165     if(cKeyword == "class") {
166         lines ~= "private:";
167         context.accessSpecifier = AccessSpecifier.Private;
168     } else
169         context.accessSpecifier = AccessSpecifier.Public;
170 
171     BitFieldInfo bitFieldInfo;
172 
173     lines ~= bitFieldInfo.header(cursor);
174 
175     context.log("Children: ", cursor.children);
176 
177     foreach(i, child; cursor.children) {
178 
179         if(child.kind == Cursor.Kind.PackedAttr) {
180             lines ~= "align(1):";
181             continue;
182         }
183 
184         lines ~= bitFieldInfo.handle(child);
185 
186         if(skipMember(child)) continue;
187 
188         const childTranslation = () {
189 
190             if(isPrivateField(child, context))
191                 return translatePrivateMember(child);
192 
193             if(child.kind == Cursor.Kind.CXXBaseSpecifier)
194                 return translateBase(i, child, context);
195 
196             return translate(child, context);
197         }();
198 
199         lines ~= childTranslation.map!(a => "    " ~ a).array;
200 
201         // Possibly deal with C11 anonymous structs/unions. See issue #29.
202         lines ~= maybeC11AnonymousRecords(cursor, child, context);
203 
204         bitFieldInfo.update(child);
205     }
206 
207     lines ~= bitFieldInfo.finish;
208     lines ~= maybeOperators(cursor, name);
209     lines ~= maybeDisableDefaultCtor(cursor, dKeyword);
210 
211     lines ~= `}`;
212 
213     return lines;
214 }
215 
216 private struct BitFieldInfo {
217 
218     import dpp.runtime.context: Context;
219     import clang: Cursor;
220 
221     /// if the last seen member was a bitfield
222     private bool lastMemberWasBitField;
223     /// the combined (summed) bitwidths of the bitfields members seen so far
224     private int totalBitWidth;
225     /// to generate new names
226     private int paddingNameIndex;
227 
228     string[] header(in Cursor cursor) @safe nothrow {
229         import std.algorithm: any;
230 
231         if(cursor.children.any!(a => a.isBitField)) {
232             // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}`
233             // would have sizeof 4, where as the corresponding bit fields in D would have
234             // size 1. So we correct here. See issue #7.
235             return [`    import std.bitmanip: bitfields;`, ``, `    align(4):`];
236         } else
237             return [];
238 
239     }
240 
241     string[] handle(in Cursor member) @safe pure nothrow {
242 
243         string[] lines;
244 
245         if(member.isBitField && !lastMemberWasBitField)
246             lines ~= `    mixin(bitfields!(`;
247 
248         if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields;
249 
250         if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) {
251             lines ~= finishBitFields;
252             lines ~= `    mixin(bitfields!(`;
253         }
254 
255         return lines;
256     }
257 
258     void update(in Cursor member) @safe pure nothrow {
259         lastMemberWasBitField = member.isBitField;
260         if(member.isBitField) totalBitWidth += member.bitWidth;
261     }
262 
263     string[] finish() @safe pure nothrow {
264         return lastMemberWasBitField ? finishBitFields : [];
265     }
266 
267     private string[] finishBitFields() @safe pure nothrow {
268         import std.conv: text;
269 
270 
271         int padding(in int totalBitWidth) {
272 
273             for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) {
274                 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth;
275             }
276 
277             assert(0, text("Could not find powerOfTwo for width ", totalBitWidth));
278         }
279 
280         const paddingBits = padding(totalBitWidth);
281 
282         string[] lines;
283 
284         if(paddingBits)
285             lines ~= text(`        uint, "`, newPaddingName, `", `, padding(totalBitWidth));
286 
287         lines ~= `    ));`;
288 
289         totalBitWidth = 0;
290 
291         return lines;
292     }
293 
294     private string newPaddingName() @safe pure nothrow {
295         import std.conv: text;
296         return text("_padding_", paddingNameIndex++);
297     }
298 }
299 
300 private bool isPrivateField(in from!"clang".Cursor cursor,
301                             in from!"dpp.runtime.context".Context context)
302     @safe
303 {
304     import clang: Cursor, AccessSpecifier;
305 
306     const isField =
307         cursor.kind == Cursor.Kind.FieldDecl ||
308         cursor.kind == Cursor.Kind.VarDecl;
309 
310     return
311         context.accessSpecifier == AccessSpecifier.Private
312         && isField
313         // The reason for this is templated types have negative sizes according
314         // to libclang, even if they're fully instantiated...
315         // So even though they're private, they can't be declared as opaque
316         // binary blobs because we don't know how large they are.
317         && cursor.type.getSizeof > 0
318         ;
319 
320 }
321 
322 // Since one can't access private members anyway, why bother translating?
323 // Declare an array of equivalent size in D, helps with the untranslatable
324 // parts of C++
325 private string[] translatePrivateMember(in from!"clang".Cursor cursor) @safe {
326     import dpp.translation.type: translateOpaque;
327 
328     return cursor.type.getSizeof > 0
329         ? [ translateOpaque(cursor.type) ~ ` ` ~ cursor.spelling ~ `;`]
330         : [];
331 }
332 
333 
334 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow {
335     import clang: Cursor;
336     return
337         !member.isDefinition
338         && member.kind != Cursor.Kind.CXXMethod
339         && member.kind != Cursor.Kind.Constructor
340         && member.kind != Cursor.Kind.Destructor
341         && member.kind != Cursor.Kind.VarDecl
342         && member.kind != Cursor.Kind.CXXBaseSpecifier
343         && member.kind != Cursor.Kind.ConversionFunction
344         && member.kind != Cursor.Kind.FunctionTemplate
345     ;
346 }
347 
348 
349 string[] translateField(in from!"clang".Cursor field,
350                         ref from!"dpp.runtime.context".Context context)
351     @safe
352 {
353 
354     import dpp.translation.dlang: maybeRename;
355     import dpp.translation.type: translate;
356     import clang: Cursor, Type;
357     import std.conv: text;
358     import std.typecons: No;
359     import std.array: replace;
360 
361     assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field));
362 
363     // The field could be a pointer to an undeclared struct or a function pointer with parameter
364     // or return types that are a pointer to an undeclared struct. We have to remember these
365     // so as to be able to declare the structs for D consumption after the fact.
366     if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context);
367 
368     // Remember the field name in case it ends up clashing with a type.
369     context.rememberField(field.spelling);
370 
371     const type = translate(field.type, context, No.translatingFunction);
372 
373     return field.isBitField
374         ? translateBitField(field, context, type)
375         : [text(type, " ", maybeRename(field, context), ";")];
376 }
377 
378 string[] translateBitField(in from!"clang".Cursor cursor,
379                            ref from!"dpp.runtime.context".Context context,
380                            in string type)
381     @safe
382 {
383     import dpp.translation.dlang: maybeRename;
384     import std.conv: text;
385 
386     auto spelling = maybeRename(cursor, context);
387     // std.bitmanip.bitfield can't handle successive mixins with
388     // no name. See issue #35.
389     if(spelling == "") spelling = context.newAnonymousMemberName;
390 
391     return [text("    ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)];
392 }
393 
394 // C allows elaborated types to appear in function parameters and member declarations
395 // if they're pointers and doesn't require a declaration for the referenced type.
396 private void maybeRememberStructsFromType(in from!"clang".Type type,
397                                           ref from!"dpp.runtime.context".Context context)
398     @safe
399 {
400     import clang: Type;
401     import std.range: only, chain;
402 
403     const pointeeType = type.pointee.canonical;
404     const isFunction =
405         pointeeType.kind == Type.Kind.FunctionProto ||
406         pointeeType.kind == Type.Kind.FunctionNoProto;
407 
408     if(pointeeType.kind == Type.Kind.Record)
409         // can't use `only` with `const` for some reason
410         maybeRememberStructs([type], context);
411     else if(isFunction)
412         maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes),
413                              context);
414 }
415 
416 
417 // C allows elaborated types to appear in function parameters and member declarations
418 // if they're pointers and doesn't require a declaration for the referenced type.
419 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context)
420     @safe if(isInputRange!R)
421 {
422     import dpp.translation.type: translate;
423     import clang: Type;
424     import std.algorithm: map, filter;
425 
426     auto structTypes = types
427         .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record)
428         .map!(a => a.pointee.canonical);
429 
430     void rememberStruct(in Type pointeeCanonicalType) {
431         import dpp.translation.type: translateElaborated;
432         import std.array: replace;
433         import std.algorithm: canFind;
434 
435         // If it's not a C elaborated type, we don't need to do anything.
436         // The only reason this code exists is because elaborated types
437         // can be used in function signatures or member declarations
438         // without a declaration for the struct itself as long as it's
439         // a pointer.
440         if(!pointeeCanonicalType.spelling.canFind("struct ") &&
441            !pointeeCanonicalType.spelling.canFind("union ") &&
442            !pointeeCanonicalType.spelling.canFind("enum "))
443             return;
444 
445         const removeConst = pointeeCanonicalType.isConstQualified
446             ? pointeeCanonicalType.spelling.replace("const ", "")
447             : pointeeCanonicalType.spelling;
448         const removeVolatile = pointeeCanonicalType.isVolatileQualified
449             ? removeConst.replace("volatile ", "")
450             : removeConst;
451 
452         context.rememberFieldStruct(translateElaborated(removeVolatile));
453     }
454 
455     foreach(structType; structTypes)
456         rememberStruct(structType);
457 }
458 
459 // if the cursor is an aggregate in C, i.e. struct, union or enum
460 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow {
461     import clang: Cursor;
462     return
463         cursor.kind == Cursor.Kind.StructDecl ||
464         cursor.kind == Cursor.Kind.UnionDecl ||
465         cursor.kind == Cursor.Kind.EnumDecl;
466 }
467 
468 
469 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor,
470                                           in from!"clang".Cursor member,
471                                           ref from!"dpp.runtime.context".Context context)
472     @safe
473 
474 {
475     import dpp.translation.type: translate, hasAnonymousSpelling;
476     import clang: Cursor, Type;
477     import std.algorithm: any, filter;
478 
479     if(member.type.kind != Type.Kind.Record || member.spelling != "") return [];
480 
481     // Either a field or an array of the type we expect
482     bool isFieldOfRightType(in Cursor member, in Cursor child) {
483         const isField =
484             child.kind == Cursor.Kind.FieldDecl &&
485             child.type.canonical == member.type.canonical;
486 
487         const isArrayOf = child.type.elementType.canonical == member.type.canonical;
488         return isField || isArrayOf;
489     }
490 
491     // Check if the parent cursor has any fields have this type.
492     // If so, we don't need to declare a dummy variable.
493     const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a));
494     if(anyFields) return [];
495 
496     string[] lines;
497     const varName = context.newAnonymousMemberName;
498 
499     //lines ~= "    " ~ translate(member.type, context) ~ " " ~  varName ~ ";";
500     const dtype = translate(member.type, context);
501     lines ~= "    " ~ dtype ~ " " ~  varName ~ ";";
502 
503     foreach(subMember; member.children) {
504         if(subMember.kind == Cursor.Kind.FieldDecl)
505             lines ~= innerFieldAccessors(varName, subMember);
506         else if(subMember.type.canonical.kind == Type.Kind.Record &&
507                 hasAnonymousSpelling(subMember.type.canonical) &&
508                 !member.children.any!(a => isFieldOfRightType(subMember, a))) {
509             foreach(subSubMember; subMember) {
510                 lines ~= "        " ~ innerFieldAccessors(varName, subSubMember);
511             }
512         }
513     }
514 
515     return lines;
516 }
517 
518 
519 // functions to emulate C11 anonymous structs/unions
520 private string[] innerFieldAccessors(in string varName, in from !"clang".Cursor subMember) @safe {
521     import std.format: format;
522     import std.algorithm: map;
523     import std.array: array;
524 
525     string[] lines;
526 
527     const fieldAccess = varName ~ "." ~ subMember.spelling;
528     const funcName = subMember.spelling;
529 
530     lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }}
531         .format(funcName, fieldAccess);
532 
533     lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }}
534         .format(funcName, fieldAccess);
535 
536     return lines.map!(a => "    " ~ a).array;
537 }
538 
539 // emit a D opCmp if the cursor has operator<, operator> and operator==
540 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name)
541     @safe
542 {
543     import dpp.translation.function_: OPERATOR_PREFIX;
544     import std.algorithm: map, any;
545     import std.array: array, replace;
546 
547     string[] lines;
548 
549     bool hasOperator(in string op) {
550         return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op);
551     }
552 
553     // if the aggregate has a parenthesis in its name it's because it's a templated
554     // struct/class, in which case the parameter has to have an exclamation mark.
555     const typeName = name.replace("(", "!(");
556 
557     if(hasOperator(">") && hasOperator("<") && hasOperator("==")) {
558         lines ~=  [
559             `int opCmp()(` ~ typeName ~ ` other) const`,
560             `{`,
561             `    if(this.opCppLess(other)) return -1;`,
562             `    if(this.opCppMore(other)) return  1;`,
563             `    return 0;`,
564             `}`,
565         ].map!(a => `    ` ~ a).array;
566     }
567 
568     if(hasOperator("!")) {
569         lines ~= [
570             `bool opCast(T: bool)() const`,
571             `{`,
572             `    return !this.opCppBang();`,
573             `}`,
574         ].map!(a => `    ` ~ a).array;
575     }
576 
577     return lines;
578 }
579 
580 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword)
581     @safe
582 {
583     import dpp.translation.function_: numParams;
584     import clang: Cursor;
585     import std.algorithm: any;
586 
587     bool hasNoArgsCtor(in Cursor child) {
588         return child.kind == Cursor.Kind.Constructor &&
589             numParams(child) == 0;
590     }
591 
592     if(dKeyword == "struct" &&
593        cursor.children.any!hasNoArgsCtor) {
594         return [`    @disable this();`];
595     }
596 
597     return [];
598 }
599 
600 
601 private string[] translateBase(size_t index,
602                                in from!"clang".Cursor cursor,
603                                ref from!"dpp.runtime.context".Context context)
604     @safe
605     in(cursor.kind == from!"clang".Cursor.Kind.CXXBaseSpecifier)
606 do
607 {
608     import dpp.translation.type: translate;
609     import std.typecons: No;
610     import std.algorithm: canFind;
611     import std.conv: text;
612 
613     const type = translate(cursor.type, context, No.translatingFunction);
614 
615     // FIXME - see it.cpp.templates.__or_
616     // Not only would that test fail if this weren't here, but the spelling of
617     // the type parameters would be completely wrong as well.
618     if(type.canFind("...")) return [];
619 
620     // FIXME - type traits failures due to inheritance
621     if(type.canFind("&")) return [];
622 
623     const fieldName = text("_base", index);
624     auto fieldDecl = type ~ " " ~ fieldName ~ ";";
625     auto maybeAliasThis = index == 0
626         ? [`alias ` ~ fieldName ~ ` this;`]
627         : [];
628 
629     return fieldDecl ~ maybeAliasThis;
630 }