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     return translateStrass(cursor, context, "class");
25 }
26 
27 // "strass" is a struct or class
28 private string[] translateStrass(in from!"clang".Cursor cursor,
29                                  ref from!"dpp.runtime.context".Context context,
30                                  in string cKeyword)
31     @safe
32 {
33     import dpp.translation.template_: templateSpelling, translateTemplateParams,
34         translateSpecialisedTemplateParams;
35     import clang: Cursor;
36     import std.typecons: Nullable, nullable;
37     import std.array: join;
38     import std.conv: text;
39 
40     assert(
41         cursor.kind == Cursor.Kind.StructDecl ||
42         cursor.kind == Cursor.Kind.ClassDecl ||
43         cursor.kind == Cursor.Kind.ClassTemplate ||
44         cursor.kind == Cursor.Kind.ClassTemplatePartialSpecialization
45     );
46 
47     const spelling = () {
48 
49         // full template
50         if(cursor.kind == Cursor.Kind.ClassTemplate)
51             return nullable(templateSpelling(cursor, translateTemplateParams(cursor, context)));
52 
53         // partial or full template specialisation
54         if(cursor.type.numTemplateArguments != -1)
55             return nullable(templateSpelling(cursor, translateSpecialisedTemplateParams(cursor, context)));
56 
57         // non-template class/struct
58         return Nullable!string();
59     }();
60 
61     const dKeyword = "struct";
62 
63     return translateAggregate(context, cursor, cKeyword, dKeyword, spelling);
64 }
65 
66 
67 
68 
69 
70 string[] translateUnion(in from!"clang".Cursor cursor,
71                         ref from!"dpp.runtime.context".Context context)
72     @safe
73 {
74     import clang: Cursor;
75     assert(cursor.kind == Cursor.Kind.UnionDecl);
76     return translateAggregate(context, cursor, "union");
77 }
78 
79 string[] translateEnum(in from!"clang".Cursor cursor,
80                        ref from!"dpp.runtime.context".Context context)
81     @safe
82 {
83     import clang: Cursor;
84     import std.typecons: nullable;
85 
86     assert(cursor.kind == Cursor.Kind.EnumDecl);
87 
88     // Translate it twice so that C semantics are the same (global names)
89     // but also have a named version for optional type correctness and
90     // reflection capabilities.
91     // This means that `enum Foo { foo, bar }` in C will become:
92     // `enum Foo { foo, bar }` _and_
93     // `enum foo = Foo.foo; enum bar = Foo.bar;` in D.
94 
95     auto enumName = context.spellingOrNickname(cursor);
96 
97     string[] lines;
98     foreach(member; cursor) {
99         if(!member.isDefinition) continue;
100         auto memName = member.spelling;
101         lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`;
102     }
103 
104     return
105         translateAggregate(context, cursor, "enum", nullable(enumName)) ~
106         lines;
107 }
108 
109 // not pure due to Cursor.opApply not being pure
110 string[] translateAggregate(
111     ref from!"dpp.runtime.context".Context context,
112     in from!"clang".Cursor cursor,
113     in string keyword,
114     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
115 )
116     @safe
117 {
118     return translateAggregate(context, cursor, keyword, keyword, spelling);
119 }
120 
121 private struct BitFieldInfo {
122 
123     import dpp.runtime.context: Context;
124     import clang: Cursor;
125 
126     /// if the last seen member was a bitfield
127     private bool lastMemberWasBitField;
128     /// the combined (summed) bitwidths of the bitfields members seen so far
129     private int totalBitWidth;
130     /// to generate new names
131     private int paddingNameIndex;
132 
133     string[] header(in Cursor cursor) @safe nothrow {
134         import std.algorithm: any;
135 
136         if(cursor.children.any!(a => a.isBitField)) {
137             // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}`
138             // would have sizeof 4, where as the corresponding bit fields in D would have
139             // size 1. So we correct here. See issue #7.
140             return [`    import std.bitmanip: bitfields;`, ``, `    align(4):`];
141         } else
142             return [];
143 
144     }
145 
146     string[] handle(in Cursor member) @safe pure nothrow {
147 
148         string[] lines;
149 
150         if(member.isBitField && !lastMemberWasBitField)
151             lines ~= `    mixin(bitfields!(`;
152 
153         if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields;
154 
155         if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) {
156             lines ~= finishBitFields;
157             lines ~= `    mixin(bitfields!(`;
158         }
159 
160         return lines;
161     }
162 
163     void update(in Cursor member) @safe pure nothrow {
164         lastMemberWasBitField = member.isBitField;
165         if(member.isBitField) totalBitWidth += member.bitWidth;
166     }
167 
168     string[] finish() @safe pure nothrow {
169         return lastMemberWasBitField ? finishBitFields : [];
170     }
171 
172     private string[] finishBitFields() @safe pure nothrow {
173         import std.conv: text;
174 
175 
176         int padding(in int totalBitWidth) {
177 
178             for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) {
179                 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth;
180             }
181 
182             assert(0, text("Could not find powerOfTwo for width ", totalBitWidth));
183         }
184 
185         const paddingBits = padding(totalBitWidth);
186 
187         string[] lines;
188 
189         if(paddingBits)
190             lines ~= text(`        uint, "`, newPaddingName, `", `, padding(totalBitWidth));
191 
192         lines ~= `    ));`;
193 
194         totalBitWidth = 0;
195 
196         return lines;
197     }
198 
199     private string newPaddingName() @safe pure nothrow {
200         import std.conv: text;
201         return text("_padding_", paddingNameIndex++);
202     }
203 
204 }
205 
206 // not pure due to Cursor.opApply not being pure
207 string[] translateAggregate(
208     ref from!"dpp.runtime.context".Context context,
209     in from!"clang".Cursor cursor,
210     in string cKeyword,
211     in string dKeyword,
212     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
213 )
214     @safe
215 {
216     import dpp.translation.translation: translate;
217     import clang: Cursor, Type;
218     import std.algorithm: map;
219     import std.array: array;
220 
221     // remember all aggregate declarations
222     context.rememberAggregate(cursor);
223 
224     const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get;
225     const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record
226         ? "static " ~ dKeyword
227         : dKeyword;
228     const firstLine = realDlangKeyword ~ ` ` ~ name;
229 
230     if(!cursor.isDefinition) return [firstLine ~ `;`];
231 
232     string[] lines;
233     lines ~= firstLine;
234     lines ~= `{`;
235 
236     if(cKeyword == "class") lines ~= "private:";
237 
238     BitFieldInfo bitFieldInfo;
239 
240     lines ~= bitFieldInfo.header(cursor);
241 
242     context.log("Children: ", cursor.children);
243 
244     foreach(member; cursor.children) {
245 
246         if(member.kind == Cursor.Kind.PackedAttr) {
247             lines ~= "align(1):";
248             continue;
249         }
250 
251         lines ~= bitFieldInfo.handle(member);
252 
253         if(skipMember(member)) continue;
254 
255         lines ~= translate(member, context).map!(a => "    " ~ a).array;
256 
257         // Possibly deal with C11 anonymous structs/unions. See issue #29.
258         lines ~= maybeC11AnonymousRecords(cursor, member, context);
259 
260         bitFieldInfo.update(member);
261     }
262 
263     lines ~= bitFieldInfo.finish;
264     lines ~= maybeOperators(cursor, name);
265     lines ~= maybeDisableDefaultCtor(cursor, dKeyword);
266 
267     lines ~= `}`;
268 
269     return lines;
270 }
271 
272 
273 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow {
274     import clang: Cursor;
275     return
276         !member.isDefinition
277         && member.kind != Cursor.Kind.CXXMethod
278         && member.kind != Cursor.Kind.Constructor
279         && member.kind != Cursor.Kind.Destructor
280         && member.kind != Cursor.Kind.VarDecl
281         && member.kind != Cursor.Kind.CXXBaseSpecifier
282         && member.kind != Cursor.Kind.ConversionFunction
283     ;
284 }
285 
286 
287 string[] translateField(in from!"clang".Cursor field,
288                         ref from!"dpp.runtime.context".Context context)
289     @safe
290 {
291 
292     import dpp.translation.dlang: maybeRename;
293     import dpp.translation.type: translate;
294     import clang: Cursor, Type;
295     import std.conv: text;
296     import std.typecons: No;
297     import std.array: replace;
298 
299     assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field));
300 
301     // The field could be a pointer to an undeclared struct or a function pointer with parameter
302     // or return types that are a pointer to an undeclared struct. We have to remember these
303     // so as to be able to declare the structs for D consumption after the fact.
304     if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context);
305 
306     // Remember the field name in case it ends up clashing with a type.
307     context.rememberField(field.spelling);
308 
309     const type = translate(field.type, context, No.translatingFunction);
310 
311     return field.isBitField
312         ? translateBitField(field, context, type)
313         : [text(type, " ", maybeRename(field, context), ";")];
314 }
315 
316 string[] translateBitField(in from!"clang".Cursor cursor,
317                            ref from!"dpp.runtime.context".Context context,
318                            in string type)
319     @safe
320 {
321     import dpp.translation.dlang: maybeRename;
322     import std.conv: text;
323 
324     auto spelling = maybeRename(cursor, context);
325     // std.bitmanip.bitfield can't handle successive mixins with
326     // no name. See issue #35.
327     if(spelling == "") spelling = context.newAnonymousMemberName;
328 
329     return [text("    ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)];
330 }
331 
332 private void maybeRememberStructsFromType(in from!"clang".Type type,
333                                           ref from!"dpp.runtime.context".Context context)
334     @safe pure
335 {
336     import clang: Type;
337     import std.range: only, chain;
338 
339     const pointeeType = type.pointee.canonical;
340     const isFunction =
341         pointeeType.kind == Type.Kind.FunctionProto ||
342         pointeeType.kind == Type.Kind.FunctionNoProto;
343 
344     if(pointeeType.kind == Type.Kind.Record)
345         maybeRememberStructs([type], context);
346     else if(isFunction)
347         maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes),
348                              context);
349 }
350 
351 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context)
352     @safe pure if(isInputRange!R)
353 {
354     import dpp.translation.type: translate;
355     import clang: Type;
356     import std.algorithm: map, filter;
357 
358     auto structTypes = types
359         .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record)
360         .map!(a => a.pointee.canonical);
361 
362     void rememberStruct(in Type pointeeCanonicalType) {
363         const translatedType = translate(pointeeCanonicalType, context);
364         // const becomes a problem if we have to define a struct at the end of all translations.
365         // See it.compile.projects.nv_alloc_ops
366         enum constPrefix = "const(";
367         const cleanedType = pointeeCanonicalType.isConstQualified
368             ? translatedType[constPrefix.length .. $-1] // unpack from const(T)
369             : translatedType;
370 
371         if(cleanedType != "va_list")
372             context.rememberFieldStruct(cleanedType);
373     }
374 
375     foreach(structType; structTypes)
376         rememberStruct(structType);
377 }
378 
379 // if the cursor is an aggregate in C, i.e. struct, union or enum
380 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow {
381     import clang: Cursor;
382     return
383         cursor.kind == Cursor.Kind.StructDecl ||
384         cursor.kind == Cursor.Kind.UnionDecl ||
385         cursor.kind == Cursor.Kind.EnumDecl;
386 }
387 
388 
389 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor,
390                                           in from!"clang".Cursor member,
391                                           ref from!"dpp.runtime.context".Context context)
392     @safe
393 
394 {
395     import dpp.translation.type: translate, hasAnonymousSpelling;
396     import clang: Cursor, Type;
397     import std.algorithm: any, filter;
398 
399     if(member.type.kind != Type.Kind.Record || member.spelling != "") return [];
400 
401     // Either a field or an array of the type we expect
402     bool isFieldOfRightType(in Cursor member, in Cursor child) {
403         const isField =
404             child.kind == Cursor.Kind.FieldDecl &&
405             child.type.canonical == member.type.canonical;
406 
407         const isArrayOf = child.type.elementType.canonical == member.type.canonical;
408         return isField || isArrayOf;
409     }
410 
411     // Check if the parent cursor has any fields have this type.
412     // If so, we don't need to declare a dummy variable.
413     const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a));
414     if(anyFields) return [];
415 
416     string[] lines;
417     const varName = context.newAnonymousMemberName;
418 
419     //lines ~= "    " ~ translate(member.type, context) ~ " " ~  varName ~ ";";
420     const dtype = translate(member.type, context);
421     lines ~= "    " ~ dtype ~ " " ~  varName ~ ";";
422 
423     foreach(subMember; member.children) {
424         if(subMember.kind == Cursor.Kind.FieldDecl)
425             lines ~= innerFieldAccessors(varName, subMember);
426         else if(subMember.type.canonical.kind == Type.Kind.Record &&
427                 hasAnonymousSpelling(subMember.type.canonical) &&
428                 !member.children.any!(a => isFieldOfRightType(subMember, a))) {
429             foreach(subSubMember; subMember) {
430                 lines ~= "        " ~ innerFieldAccessors(varName, subSubMember);
431             }
432         }
433     }
434 
435     return lines;
436 }
437 
438 
439 // functions to emulate C11 anonymous structs/unions
440 private string[] innerFieldAccessors(in string varName, in from !"clang".Cursor subMember) @safe {
441     import std.format: format;
442     import std.algorithm: map;
443     import std.array: array;
444 
445     string[] lines;
446 
447     const fieldAccess = varName ~ "." ~ subMember.spelling;
448     const funcName = subMember.spelling;
449 
450     lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }}
451         .format(funcName, fieldAccess);
452 
453     lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }}
454         .format(funcName, fieldAccess);
455 
456     return lines.map!(a => "    " ~ a).array;
457 }
458 
459 // emit a D opCmp if the cursor has operator<, operator> and operator==
460 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name)
461     @safe
462 {
463     import dpp.translation.function_: OPERATOR_PREFIX;
464     import std.algorithm: map, any;
465     import std.array: array;
466 
467     string[] lines;
468 
469     bool hasOperator(in string op) {
470         return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op);
471     }
472 
473     if(hasOperator(">") && hasOperator("<") && hasOperator("==")) {
474         lines ~=  [
475             `int opCmp()(` ~ name ~ ` other) const`,
476             `{`,
477             `    if(this.opCppLess(other)) return -1;`,
478             `    if(this.opCppMore(other)) return  1;`,
479             `    return 0;`,
480             `}`,
481         ].map!(a => `    ` ~ a).array;
482     }
483 
484     if(hasOperator("!")) {
485         lines ~= [
486             `bool opCast(T: bool)() const`,
487             `{`,
488             `    return !this.opCppBang();`,
489             `}`,
490         ].map!(a => `    ` ~ a).array;
491     }
492 
493     return lines;
494 }
495 
496 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword)
497     @safe
498 {
499     import clang: Cursor;
500     import std.algorithm: any;
501 
502     if(dKeyword == "struct" &&
503        cursor.children.any!(a => a.kind == Cursor.Kind.Constructor)) {
504         return [`    @disable this();`];
505     }
506 
507     return [];
508 }