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 }