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 }