1 /**
2    Translate aggregates
3  */
4 module dpp.cursor.aggregate;
5 
6 import dpp.from;
7 import std.range: isInputRange;
8 
9 
10 string[] translateStruct(in from!"clang".Cursor cursor,
11                          ref from!"dpp.runtime.context".Context context)
12     @safe
13 {
14     import clang: Cursor;
15     assert(cursor.kind == Cursor.Kind.StructDecl);
16     return translateAggregate(context, cursor, "struct");
17 }
18 
19 string[] translateClass(in from!"clang".Cursor cursor,
20                         ref from!"dpp.runtime.context".Context context)
21     @safe
22 {
23     import clang: Cursor;
24     import std.typecons: Nullable, nullable;
25     import std.algorithm: map, filter;
26     import std.array: join;
27 
28     assert(cursor.kind == Cursor.Kind.ClassDecl || cursor.kind == Cursor.Kind.ClassTemplate);
29 
30     string translateTemplateParam(in Cursor cursor) {
31         import dpp.type: translate;
32         const maybeType = cursor.kind == Cursor.Kind.TemplateTypeParameter
33             ? ""
34             : translate(cursor.type, context) ~ " ";
35         return maybeType ~ cursor.spelling;
36     }
37 
38     auto templateParams = cursor
39         .children
40         .filter!(a => a.kind == Cursor.Kind.TemplateTypeParameter || a.kind == Cursor.Kind.NonTypeTemplateParameter)
41         .map!translateTemplateParam
42         ;
43 
44     const spelling = cursor.kind == Cursor.Kind.ClassTemplate
45         ? nullable(cursor.spelling ~ `(` ~ templateParams.join(", ") ~ `)`)
46         : Nullable!string();
47 
48     return translateAggregate(context, cursor, "class", "struct", spelling);
49 }
50 
51 string[] translateUnion(in from!"clang".Cursor cursor,
52                         ref from!"dpp.runtime.context".Context context)
53     @safe
54 {
55     import clang: Cursor;
56     assert(cursor.kind == Cursor.Kind.UnionDecl);
57     return translateAggregate(context, cursor, "union");
58 }
59 
60 string[] translateEnum(in from!"clang".Cursor cursor,
61                        ref from!"dpp.runtime.context".Context context)
62     @safe
63 {
64     import clang: Cursor;
65     import std.typecons: nullable;
66 
67     assert(cursor.kind == Cursor.Kind.EnumDecl);
68 
69     // Translate it twice so that C semantics are the same (global names)
70     // but also have a named version for optional type correctness and
71     // reflection capabilities.
72     // This means that `enum Foo { foo, bar }` in C will become:
73     // `enum Foo { foo, bar }` _and_
74     // `enum foo = Foo.foo; enum bar = Foo.bar;` in D.
75 
76     auto enumName = context.spellingOrNickname(cursor);
77 
78     string[] lines;
79     foreach(member; cursor) {
80         if(!member.isDefinition) continue;
81         auto memName = member.spelling;
82         lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`;
83     }
84 
85     return
86         translateAggregate(context, cursor, "enum", nullable(enumName)) ~
87         lines;
88 }
89 
90 // not pure due to Cursor.opApply not being pure
91 string[] translateAggregate(
92     ref from!"dpp.runtime.context".Context context,
93     in from!"clang".Cursor cursor,
94     in string keyword,
95     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
96 )
97     @safe
98 {
99     return translateAggregate(context, cursor, keyword, keyword, spelling);
100 }
101 
102 // not pure due to Cursor.opApply not being pure
103 string[] translateAggregate(
104     ref from!"dpp.runtime.context".Context context,
105     in from!"clang".Cursor cursor,
106     in string cKeyword,
107     in string dKeyword,
108     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
109 )
110     @safe
111 {
112     import dpp.cursor.translation: translate;
113     import clang: Cursor;
114     import std.algorithm: map, any;
115     import std.array: array;
116     import std.conv: text;
117 
118     // remember all aggregate declarations
119     context.rememberAggregate(cursor);
120 
121     const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get;
122     const firstLine = dKeyword ~ ` ` ~ name;
123 
124     if(!cursor.isDefinition) return [firstLine ~ `;`];
125 
126     string[] lines;
127     lines ~= firstLine;
128     lines ~= `{`;
129 
130     if(cKeyword == "class") lines ~= "private:";
131 
132     if(cursor.children.any!(a => a.isBitField)) {
133         // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}`
134         // would have sizeof 4, where as the corresponding bit fields in D would have
135         // size 1. So we correct here. See issue #7.
136         lines ~= [`    import std.bitmanip: bitfields;`, ``, `    align(4):`];
137     }
138 
139     // if the last seen member was a bitfield
140     bool lastMemberWasBitField = false;
141     // the combined (summed) bitwidths of the bitfields members seen so far
142     int totalBitWidth = 0;
143 
144     void finishBitFields() {
145         lines ~= text(`        uint, "", `, padding(totalBitWidth));
146         lines ~= `    ));`;
147         totalBitWidth = 0;
148     }
149 
150     bool skipMember(in Cursor member) {
151         return
152             !member.isDefinition &&
153             member.kind != Cursor.Kind.CXXMethod &&
154             member.kind != Cursor.Kind.Constructor &&
155             member.kind != Cursor.Kind.Destructor &&
156             member.kind != Cursor.Kind.VarDecl &&
157             member.kind != Cursor.Kind.CXXBaseSpecifier;
158     }
159 
160     context.log("Children: ", cursor.children);
161 
162     foreach(member; cursor.children) {
163 
164         if(member.kind == Cursor.Kind.PackedAttr) {
165             lines ~= "align(1):";
166             continue;
167         }
168 
169         if(member.isBitField && !lastMemberWasBitField)
170             lines ~= `    mixin(bitfields!(`;
171 
172         if(!member.isBitField && lastMemberWasBitField) finishBitFields;
173 
174         if(skipMember(member)) continue;
175 
176         lines ~= translate(member, context).map!(a => "    " ~ a).array;
177 
178         lastMemberWasBitField = member.isBitField;
179         if(member.isBitField) totalBitWidth += member.bitWidth;
180     }
181 
182     if(lastMemberWasBitField) finishBitFields;
183 
184     lines ~= `}`;
185 
186     return lines;
187 }
188 
189 private int padding(in int totalBitWidth) @safe @nogc pure nothrow {
190     for(int powerOfTwo = 8; powerOfTwo < 64; powerOfTwo *= 2) {
191         if(powerOfTwo > totalBitWidth) return powerOfTwo - totalBitWidth;
192     }
193 
194     assert(0);
195 }
196 
197 
198 string[] translateField(in from!"clang".Cursor field,
199                         ref from!"dpp.runtime.context".Context context)
200     @safe
201 {
202 
203     import dpp.cursor.dlang: maybeRename;
204     import dpp.type: translate;
205     import clang: Cursor, Type;
206     import std.conv: text;
207     import std.typecons: No;
208     import std.array: replace;
209 
210     assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field));
211 
212     // The field could be a pointer to an undeclared struct or a function pointer with parameter
213     // or return types that are a pointer to an undeclared struct. We have to remember these
214     // so as to be able to declare the structs for D consumption after the fact.
215     if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context);
216 
217     // Remember the field name in case it ends up clashing with a type.
218     context.rememberField(field.spelling);
219 
220     const type = translate(field.type, context, No.translatingFunction);
221 
222     return field.isBitField
223         ? [text("    ", type, `, "`, maybeRename(field, context), `", `, field.bitWidth, `,`)]
224         : [text(type, " ", maybeRename(field, context), ";")];
225 }
226 
227 
228 private void maybeRememberStructsFromType(in from!"clang".Type type,
229                                           ref from!"dpp.runtime.context".Context context)
230     @safe pure
231 {
232     import clang: Type;
233     import std.range: only, chain;
234 
235     const pointeeType = type.pointee.canonical;
236     const isFunction =
237         pointeeType.kind == Type.Kind.FunctionProto ||
238         pointeeType.kind == Type.Kind.FunctionNoProto;
239 
240     if(pointeeType.kind == Type.Kind.Record)
241         maybeRememberStructs([type], context);
242     else if(isFunction)
243         maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes),
244                              context);
245 }
246 
247 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context)
248     @safe pure if(isInputRange!R)
249 {
250     import dpp.type: translate;
251     import clang: Type;
252     import std.algorithm: map, filter;
253 
254     auto structTypes = types
255         .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record)
256         .map!(a => a.pointee.canonical);
257 
258     void rememberStruct(in Type pointeeCanonicalType) {
259         const translatedType = translate(pointeeCanonicalType, context);
260         // const becomes a problem if we have to define a struct at the end of all translations.
261         // See it.compile.projects.nv_alloc_ops
262         enum constPrefix = "const(";
263         const cleanedType = pointeeCanonicalType.isConstQualified
264             ? translatedType[constPrefix.length .. $-1] // unpack from const(T)
265             : translatedType;
266 
267         if(cleanedType != "va_list")
268             context.rememberFieldStruct(cleanedType);
269     }
270 
271     foreach(structType; structTypes)
272         rememberStruct(structType);
273 }
274 
275 // if the cursor is an aggregate in C, i.e. struct, union or enum
276 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow {
277     import clang: Cursor;
278     return
279         cursor.kind == Cursor.Kind.StructDecl ||
280         cursor.kind == Cursor.Kind.UnionDecl ||
281         cursor.kind == Cursor.Kind.EnumDecl;
282 }