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