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 clang: Cursor;
34     import std.typecons: Nullable, nullable;
35     import std.array: join;
36     import std.conv: text;
37 
38     assert(
39         cursor.kind == Cursor.Kind.StructDecl ||
40         cursor.kind == Cursor.Kind.ClassDecl ||
41         cursor.kind == Cursor.Kind.ClassTemplate ||
42         cursor.kind == Cursor.Kind.ClassTemplatePartialSpecialization
43     );
44 
45     string templateParamList(R)(R range) {
46         return `(` ~ () @trusted { return range.join(", "); }() ~ `)`;
47     }
48 
49     string templateSpelling(R)(in Cursor cursor, R range) {
50         return cursor.spelling ~ templateParamList(range);
51     }
52 
53     const spelling = () {
54 
55         // full template
56         if(cursor.kind == Cursor.Kind.ClassTemplate)
57             return nullable(templateSpelling(cursor, translateTemplateParams(cursor, context)));
58 
59         // partial or full template specialisation
60         if(cursor.type.numTemplateArguments != -1)
61             return nullable(templateSpelling(cursor, translateSpecialisedTemplateParams(cursor, context)));
62 
63         // non-template class/struct
64         return Nullable!string();
65     }();
66 
67     const dKeyword = "struct";
68 
69     return translateAggregate(context, cursor, cKeyword, dKeyword, spelling);
70 }
71 
72 
73 // Deal with full and partial template specialisations
74 // returns a range of string
75 private string[] translateSpecialisedTemplateParams(in from!"clang".Cursor cursor,
76                                                     ref from!"dpp.runtime.context".Context context)
77     @safe
78 {
79     import dpp.translation.type: translate;
80     import clang: Type;
81     import std.algorithm: map;
82     import std.range: iota;
83     import std.array: array, join;
84 
85     assert(cursor.type.numTemplateArguments != -1);
86 
87     if(isFromVariadicTemplate(cursor))
88         return translateSpecialisedTemplateParamsVariadic(cursor, context);
89 
90     // get the original list of template parameters and translate them
91     // e.g. template<bool, bool, typename> -> (bool V0, bool V1, T)
92     const translatedTemplateParams = () @trusted {
93         return translateTemplateParams(cursor, context)
94         .array;
95     }();
96 
97     // e.g. template<> struct foo<false, true, int32_t> -> 0:false, 1:true, 2: int
98     string translateTemplateParamSpecialisation(in Type type, in int index) {
99         return type.kind == Type.Kind.Invalid
100             ? templateParameterSpelling(cursor, index)
101             : translate(type, context);
102     }
103 
104     // e.g. for template<> struct foo<false, true, int32_t>
105     // 0 -> bool V0: false, 1 -> bool V1: true, 2 -> T0: int
106     string element(in Type type, in int index) {
107         string ret = translatedTemplateParams[index];  // e.g. `T`,  `bool V0`
108         const maybeSpecialisation = translateTemplateParamSpecialisation(type, index);
109 
110         // type template arguments may be:
111         // Invalid - value (could be specialised or not)
112         // Unexposed - non-specialised type or
113         // anything else - specialised type
114         // The trick is figuring out if a value is specialised or not
115         const isValue = type.kind == Type.Kind.Invalid;
116         const isType = !isValue;
117         const isSpecialised =
118             (isValue && isValueOfType(cursor, context, index, maybeSpecialisation))
119             ||
120             (isType && type.kind != Type.Kind.Unexposed);
121 
122         if(isSpecialised) ret ~= ": " ~ maybeSpecialisation;
123 
124         return ret;
125     }
126 
127     return () @trusted {
128         return
129             cursor.type.numTemplateArguments
130             .iota
131             .map!(i => element(cursor.type.typeTemplateArgument(i), i))
132             .array
133             ;
134     }();
135 }
136 
137 // FIXME: refactor
138 private auto translateSpecialisedTemplateParamsVariadic(in from!"clang".Cursor cursor,
139                                                         ref from!"dpp.runtime.context".Context context)
140     @safe
141 {
142     import dpp.translation.type: translate;
143 
144     assert(isFromVariadicTemplate(cursor));
145     assert(cursor.type.numTemplateArguments != -1);
146 
147     string[] ret;
148 
149     foreach(i; 0 .. cursor.type.numTemplateArguments) {
150         ret ~= translate(cursor.type.typeTemplateArgument(i), context);
151     }
152 
153     return ret;
154 }
155 
156 // In the case cursor is a partial or full template specialisation,
157 // check to see if `maybeSpecialisation` can be converted to the
158 // indexth template parater of the cursor's original template.
159 // If it can, then it's a value of that type.
160 private bool isValueOfType(
161     in from!"clang".Cursor cursor,
162     ref from!"dpp.runtime.context".Context context,
163     in int index,
164     in string maybeSpecialisation,
165     )
166     @safe
167 {
168     import dpp.translation.type: translate;
169     import std.array: array;
170     import std.exception: collectException;
171     import std.conv: to;
172 
173     // the original template cursor (no specialisations)
174     const templateCursor = cursor.specializedCursorTemplate;
175     // the type of the indexth template parameter
176     const templateParamCursor = () @trusted { return templateCursor.templateParams.array[index]; }();
177     // the D translation of that type
178     const dtype = translate(templateParamCursor.type, context);
179 
180     Exception conversionException;
181 
182     void tryConvert(T)() {
183         conversionException = collectException(maybeSpecialisation.to!T);
184     }
185 
186     switch(dtype) {
187         default: throw new Exception("isValueOfType cannot handle type `" ~ dtype ~ "`");
188         case "bool":   tryConvert!bool;   break;
189         case "char":   tryConvert!char;   break;
190         case "wchar":  tryConvert!wchar;  break;
191         case "dchar":  tryConvert!dchar;  break;
192         case "short":  tryConvert!short;  break;
193         case "ushort": tryConvert!ushort; break;
194         case "int":    tryConvert!int;    break;
195         case "uint":   tryConvert!uint;   break;
196         case "long":   tryConvert!long;   break;
197         case "ulong":  tryConvert!long;   break;
198     }
199 
200     return conversionException is null;
201 }
202 
203 // returns the indexth template parameter value from a specialised
204 // template struct/class cursor (full or partial)
205 // e.g. template<> struct Foo<int, 42, double> -> 1: 42
206 private string templateParameterSpelling(in from!"clang".Cursor cursor, int index) {
207     import std.algorithm: findSkip, until, OpenRight;
208     import std.array: empty, save, split, array;
209     import std.conv: text;
210 
211     auto spelling = cursor.type.spelling.dup;
212     if(!spelling.findSkip("<")) return "";
213 
214     auto templateParams = spelling.until(">", OpenRight.yes).array.split(", ");
215 
216     return templateParams[index].text;
217 }
218 
219 // Translates a C++ template parameter (value or type) to a D declaration
220 // e.g. template<typename, bool, typename> -> ["T0", "bool V0", "T1"]
221 // Returns a range of string
222 private auto translateTemplateParams(in from!"clang".Cursor cursor,
223                                      ref from!"dpp.runtime.context".Context context)
224     @safe
225 {
226     import dpp.translation.type: translate;
227     import clang: Cursor;
228     import std.conv: text;
229     import std.algorithm: map, filter;
230     import std.array: array;
231     import std.range: enumerate;
232 
233     int templateParamIndex;  // used to generate names when there are none
234 
235     string newTemplateParamName() {
236         // FIXME
237         // the naming convention is to match what libclang gives, but there's no
238         // guarantee that it'll always match.
239         return text("type_parameter_0_", templateParamIndex++);
240     }
241 
242     string translateTemplateParam(in Cursor cursor) {
243         import dpp.translation.type: translate;
244 
245         // The template parameter might be a value (bool, int, etc.)
246         // or a type. If it's a value we get its type here.
247         const maybeType = cursor.kind == Cursor.Kind.TemplateTypeParameter
248             ? ""  // a type doesn't have a type
249             : translate(cursor.type, context) ~ " ";
250 
251         // D requires template parameters to have names
252         const spelling = cursor.spelling == "" ? newTemplateParamName : cursor.spelling;
253 
254         // e.g. "bool param", "T0"
255         return maybeType ~ spelling;
256     }
257 
258     auto templateParams = templateParams(cursor);
259     auto translated = templateParams.map!translateTemplateParam.array;
260 
261     // might need to be a variadic parameter
262     string maybeVariadic(in long index, in string name) {
263         return cursor.isVariadicTemplate && index == translated.length -1
264             // If it's variadic, come up with a new name in case it's variadic
265             // values. D doesn't really care.
266             ? newTemplateParamName ~ "..."
267             : name;
268     }
269 
270     return () @trusted {
271         return translated
272             .enumerate
273             .map!(a => maybeVariadic(a[0], a[1]))
274         ;
275     }();
276 }
277 
278 // returns a range of cursors
279 private auto templateParams(in from!"clang".Cursor cursor)
280     @safe
281 {
282 
283     import clang: Cursor;
284     import std.algorithm: filter;
285 
286     const templateCursor = cursor.kind == Cursor.Kind.ClassTemplate
287         ? cursor
288         : cursor.specializedCursorTemplate;
289 
290     return templateCursor
291         .children
292         .filter!(a => a.kind == Cursor.Kind.TemplateTypeParameter || a.kind == Cursor.Kind.NonTypeTemplateParameter)
293         ;
294 }
295 
296 // If the original template is variadic
297 private bool isFromVariadicTemplate(in from!"clang".Cursor cursor) @safe {
298     return isVariadicTemplate(cursor.specializedCursorTemplate);
299 }
300 
301 private bool isVariadicTemplate(in from!"clang".Cursor cursor) @safe {
302     import clang: Cursor, Token;
303     import std.array: array;
304     import std.algorithm: canFind;
305 
306     const templateParamChildren = () @trusted { return templateParams(cursor).array; }();
307 
308     return
309         templateParamChildren.length > 0 &&
310         (templateParamChildren[$-1].kind == Cursor.Kind.TemplateTypeParameter ||
311          templateParamChildren[$-1].kind == Cursor.Kind.NonTypeTemplateParameter) &&
312         cursor.tokens.canFind(Token(Token.Kind.Punctuation, "..."));
313 }
314 
315 
316 
317 string[] translateUnion(in from!"clang".Cursor cursor,
318                         ref from!"dpp.runtime.context".Context context)
319     @safe
320 {
321     import clang: Cursor;
322     assert(cursor.kind == Cursor.Kind.UnionDecl);
323     return translateAggregate(context, cursor, "union");
324 }
325 
326 string[] translateEnum(in from!"clang".Cursor cursor,
327                        ref from!"dpp.runtime.context".Context context)
328     @safe
329 {
330     import clang: Cursor;
331     import std.typecons: nullable;
332 
333     assert(cursor.kind == Cursor.Kind.EnumDecl);
334 
335     // Translate it twice so that C semantics are the same (global names)
336     // but also have a named version for optional type correctness and
337     // reflection capabilities.
338     // This means that `enum Foo { foo, bar }` in C will become:
339     // `enum Foo { foo, bar }` _and_
340     // `enum foo = Foo.foo; enum bar = Foo.bar;` in D.
341 
342     auto enumName = context.spellingOrNickname(cursor);
343 
344     string[] lines;
345     foreach(member; cursor) {
346         if(!member.isDefinition) continue;
347         auto memName = member.spelling;
348         lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`;
349     }
350 
351     return
352         translateAggregate(context, cursor, "enum", nullable(enumName)) ~
353         lines;
354 }
355 
356 // not pure due to Cursor.opApply not being pure
357 string[] translateAggregate(
358     ref from!"dpp.runtime.context".Context context,
359     in from!"clang".Cursor cursor,
360     in string keyword,
361     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
362 )
363     @safe
364 {
365     return translateAggregate(context, cursor, keyword, keyword, spelling);
366 }
367 
368 private struct BitFieldInfo {
369 
370     import dpp.runtime.context: Context;
371     import clang: Cursor;
372 
373     /// if the last seen member was a bitfield
374     private bool lastMemberWasBitField;
375     /// the combined (summed) bitwidths of the bitfields members seen so far
376     private int totalBitWidth;
377     /// to generate new names
378     private int paddingNameIndex;
379 
380     string[] header(in Cursor cursor) @safe nothrow {
381         import std.algorithm: any;
382 
383         if(cursor.children.any!(a => a.isBitField)) {
384             // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}`
385             // would have sizeof 4, where as the corresponding bit fields in D would have
386             // size 1. So we correct here. See issue #7.
387             return [`    import std.bitmanip: bitfields;`, ``, `    align(4):`];
388         } else
389             return [];
390 
391     }
392 
393     string[] handle(in Cursor member) @safe pure nothrow {
394 
395         string[] lines;
396 
397         if(member.isBitField && !lastMemberWasBitField)
398             lines ~= `    mixin(bitfields!(`;
399 
400         if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields;
401 
402         if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) {
403             lines ~= finishBitFields;
404             lines ~= `    mixin(bitfields!(`;
405         }
406 
407         return lines;
408     }
409 
410     void update(in Cursor member) @safe pure nothrow {
411         lastMemberWasBitField = member.isBitField;
412         if(member.isBitField) totalBitWidth += member.bitWidth;
413     }
414 
415     string[] finish() @safe pure nothrow {
416         return lastMemberWasBitField ? finishBitFields : [];
417     }
418 
419     private string[] finishBitFields() @safe pure nothrow {
420         import std.conv: text;
421 
422 
423         int padding(in int totalBitWidth) {
424 
425             for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) {
426                 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth;
427             }
428 
429             assert(0, text("Could not find powerOfTwo for width ", totalBitWidth));
430         }
431 
432         const paddingBits = padding(totalBitWidth);
433 
434         string[] lines;
435 
436         if(paddingBits)
437             lines ~= text(`        uint, "`, newPaddingName, `", `, padding(totalBitWidth));
438 
439         lines ~= `    ));`;
440 
441         totalBitWidth = 0;
442 
443         return lines;
444     }
445 
446     private string newPaddingName() @safe pure nothrow {
447         import std.conv: text;
448         return text("_padding_", paddingNameIndex++);
449     }
450 
451 }
452 
453 // not pure due to Cursor.opApply not being pure
454 string[] translateAggregate(
455     ref from!"dpp.runtime.context".Context context,
456     in from!"clang".Cursor cursor,
457     in string cKeyword,
458     in string dKeyword,
459     in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string()
460 )
461     @safe
462 {
463     import dpp.translation.translation: translate;
464     import clang: Cursor, Type;
465     import std.algorithm: map;
466     import std.array: array;
467 
468     // remember all aggregate declarations
469     context.rememberAggregate(cursor);
470 
471     const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get;
472     const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record
473         ? "static " ~ dKeyword
474         : dKeyword;
475     const firstLine = realDlangKeyword ~ ` ` ~ name;
476 
477     if(!cursor.isDefinition) return [firstLine ~ `;`];
478 
479     string[] lines;
480     lines ~= firstLine;
481     lines ~= `{`;
482 
483     if(cKeyword == "class") lines ~= "private:";
484 
485     BitFieldInfo bitFieldInfo;
486 
487     lines ~= bitFieldInfo.header(cursor);
488 
489     context.log("Children: ", cursor.children);
490 
491     foreach(member; cursor.children) {
492 
493         if(member.kind == Cursor.Kind.PackedAttr) {
494             lines ~= "align(1):";
495             continue;
496         }
497 
498         lines ~= bitFieldInfo.handle(member);
499 
500         if(skipMember(member)) continue;
501 
502         lines ~= translate(member, context).map!(a => "    " ~ a).array;
503 
504         // Possibly deal with C11 anonymous structs/unions. See issue #29.
505         lines ~= maybeC11AnonymousRecords(cursor, member, context);
506 
507         bitFieldInfo.update(member);
508     }
509 
510     lines ~= bitFieldInfo.finish;
511     lines ~= maybeOperators(cursor, name);
512     lines ~= maybeDisableDefaultCtor(cursor, dKeyword);
513 
514     lines ~= `}`;
515 
516     return lines;
517 }
518 
519 
520 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow {
521     import clang: Cursor;
522     return
523         !member.isDefinition
524         && member.kind != Cursor.Kind.CXXMethod
525         && member.kind != Cursor.Kind.Constructor
526         && member.kind != Cursor.Kind.Destructor
527         && member.kind != Cursor.Kind.VarDecl
528         && member.kind != Cursor.Kind.CXXBaseSpecifier
529         && member.kind != Cursor.Kind.ConversionFunction
530     ;
531 }
532 
533 
534 string[] translateField(in from!"clang".Cursor field,
535                         ref from!"dpp.runtime.context".Context context)
536     @safe
537 {
538 
539     import dpp.translation.dlang: maybeRename;
540     import dpp.translation.type: translate;
541     import clang: Cursor, Type;
542     import std.conv: text;
543     import std.typecons: No;
544     import std.array: replace;
545 
546     assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field));
547 
548     // The field could be a pointer to an undeclared struct or a function pointer with parameter
549     // or return types that are a pointer to an undeclared struct. We have to remember these
550     // so as to be able to declare the structs for D consumption after the fact.
551     if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context);
552 
553     // Remember the field name in case it ends up clashing with a type.
554     context.rememberField(field.spelling);
555 
556     const type = translate(field.type, context, No.translatingFunction);
557 
558     return field.isBitField
559         ? translateBitField(field, context, type)
560         : [text(type, " ", maybeRename(field, context), ";")];
561 }
562 
563 string[] translateBitField(in from!"clang".Cursor cursor,
564                            ref from!"dpp.runtime.context".Context context,
565                            in string type)
566     @safe
567 {
568     import dpp.translation.dlang: maybeRename;
569     import std.conv: text;
570 
571     auto spelling = maybeRename(cursor, context);
572     // std.bitmanip.bitfield can't handle successive mixins with
573     // no name. See issue #35.
574     if(spelling == "") spelling = context.newAnonymousMemberName;
575 
576     return [text("    ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)];
577 }
578 
579 private void maybeRememberStructsFromType(in from!"clang".Type type,
580                                           ref from!"dpp.runtime.context".Context context)
581     @safe pure
582 {
583     import clang: Type;
584     import std.range: only, chain;
585 
586     const pointeeType = type.pointee.canonical;
587     const isFunction =
588         pointeeType.kind == Type.Kind.FunctionProto ||
589         pointeeType.kind == Type.Kind.FunctionNoProto;
590 
591     if(pointeeType.kind == Type.Kind.Record)
592         maybeRememberStructs([type], context);
593     else if(isFunction)
594         maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes),
595                              context);
596 }
597 
598 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context)
599     @safe pure if(isInputRange!R)
600 {
601     import dpp.translation.type: translate;
602     import clang: Type;
603     import std.algorithm: map, filter;
604 
605     auto structTypes = types
606         .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record)
607         .map!(a => a.pointee.canonical);
608 
609     void rememberStruct(in Type pointeeCanonicalType) {
610         const translatedType = translate(pointeeCanonicalType, context);
611         // const becomes a problem if we have to define a struct at the end of all translations.
612         // See it.compile.projects.nv_alloc_ops
613         enum constPrefix = "const(";
614         const cleanedType = pointeeCanonicalType.isConstQualified
615             ? translatedType[constPrefix.length .. $-1] // unpack from const(T)
616             : translatedType;
617 
618         if(cleanedType != "va_list")
619             context.rememberFieldStruct(cleanedType);
620     }
621 
622     foreach(structType; structTypes)
623         rememberStruct(structType);
624 }
625 
626 // if the cursor is an aggregate in C, i.e. struct, union or enum
627 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow {
628     import clang: Cursor;
629     return
630         cursor.kind == Cursor.Kind.StructDecl ||
631         cursor.kind == Cursor.Kind.UnionDecl ||
632         cursor.kind == Cursor.Kind.EnumDecl;
633 }
634 
635 
636 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor,
637                                           in from!"clang".Cursor member,
638                                           ref from!"dpp.runtime.context".Context context)
639     @safe
640 
641 {
642     import dpp.translation.type: translate, hasAnonymousSpelling;
643     import clang: Cursor, Type;
644     import std.algorithm: any, filter;
645 
646     if(member.type.kind != Type.Kind.Record || member.spelling != "") return [];
647 
648     // Either a field or an array of the type we expect
649     bool isFieldOfRightType(in Cursor member, in Cursor child) {
650         const isField =
651             child.kind == Cursor.Kind.FieldDecl &&
652             child.type.canonical == member.type.canonical;
653 
654         const isArrayOf = child.type.elementType.canonical == member.type.canonical;
655         return isField || isArrayOf;
656     }
657 
658     // Check if the parent cursor has any fields have this type.
659     // If so, we don't need to declare a dummy variable.
660     const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a));
661     if(anyFields) return [];
662 
663     string[] lines;
664     const varName = context.newAnonymousMemberName;
665 
666     //lines ~= "    " ~ translate(member.type, context) ~ " " ~  varName ~ ";";
667     const dtype = translate(member.type, context);
668     lines ~= "    " ~ dtype ~ " " ~  varName ~ ";";
669 
670     foreach(subMember; member.children) {
671         if(subMember.kind == Cursor.Kind.FieldDecl)
672             lines ~= innerFieldAccessors(varName, subMember);
673         else if(subMember.type.canonical.kind == Type.Kind.Record &&
674                 hasAnonymousSpelling(subMember.type.canonical) &&
675                 !member.children.any!(a => isFieldOfRightType(subMember, a))) {
676             foreach(subSubMember; subMember) {
677                 lines ~= "        " ~ innerFieldAccessors(varName, subSubMember);
678             }
679         }
680     }
681 
682     return lines;
683 }
684 
685 
686 // functions to emulate C11 anonymous structs/unions
687 private string[] innerFieldAccessors(in string varName, in from !"clang".Cursor subMember) @safe {
688     import std.format: format;
689     import std.algorithm: map;
690     import std.array: array;
691 
692     string[] lines;
693 
694     const fieldAccess = varName ~ "." ~ subMember.spelling;
695     const funcName = subMember.spelling;
696 
697     lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }}
698         .format(funcName, fieldAccess);
699 
700     lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }}
701         .format(funcName, fieldAccess);
702 
703     return lines.map!(a => "    " ~ a).array;
704 }
705 
706 // emit a D opCmp if the cursor has operator<, operator> and operator==
707 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name)
708     @safe
709 {
710     import dpp.translation.function_: OPERATOR_PREFIX;
711     import std.algorithm: map, any;
712     import std.array: array;
713 
714     string[] lines;
715 
716     bool hasOperator(in string op) {
717         return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op);
718     }
719 
720     if(hasOperator(">") && hasOperator("<") && hasOperator("==")) {
721         lines ~=  [
722             `int opCmp(` ~ name ~ ` other) const`,
723             `{`,
724             `    if(this.opCppLess(other)) return -1;`,
725             `    if(this.opCppMore(other)) return  1;`,
726             `    return 0;`,
727             `}`,
728         ].map!(a => `    ` ~ a).array;
729     }
730 
731     if(hasOperator("!")) {
732         lines ~= [
733             `bool opCast(T: bool)() const`,
734             `{`,
735             `    return !this.opCppBang();`,
736             `}`,
737         ].map!(a => `    ` ~ a).array;
738     }
739 
740     return lines;
741 }
742 
743 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword)
744     @safe
745 {
746     import clang: Cursor;
747     import std.algorithm: any;
748 
749     if(dKeyword == "struct" &&
750        cursor.children.any!(a => a.kind == Cursor.Kind.Constructor)) {
751         return [`    @disable this();`];
752     }
753 
754     return [];
755 }