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 = "struct"; 69 70 return translateAggregate(context, cursor, cKeyword, dKeyword, spelling); 71 } 72 73 74 string[] translateUnion(in from!"clang".Cursor cursor, 75 ref from!"dpp.runtime.context".Context context) 76 @safe 77 in(cursor.kind == from!"clang".Cursor.Kind.UnionDecl) 78 do 79 { 80 import clang: Cursor; 81 return translateAggregate(context, cursor, "union"); 82 } 83 84 85 string[] translateEnum(in from!"clang".Cursor cursor, 86 ref from!"dpp.runtime.context".Context context) 87 @safe 88 in(cursor.kind == from!"clang".Cursor.Kind.EnumDecl) 89 do 90 { 91 import dpp.translation.dlang: maybeRename; 92 import clang: Cursor, Token; 93 import std.typecons: nullable; 94 import std.algorithm: canFind; 95 96 const enumName = context.spellingOrNickname(cursor); 97 string[] lines; 98 99 const isEnumClass = cursor.tokens.canFind(Token(Token.Kind.Keyword, "class")); 100 101 if(!isEnumClass && !context.options.alwaysScopedEnums) { 102 // Translate it twice so that C semantics are the same (global names) 103 // but also have a named version for optional type correctness and 104 // reflection capabilities. 105 // This means that `enum Foo { foo, bar }` in C will become: 106 // `enum Foo { foo, bar }` _and_ 107 // `enum foo = Foo.foo; enum bar = Foo.bar;` in D. 108 109 foreach(member; cursor) { 110 if(!member.isDefinition) continue; 111 auto memName = maybeRename(member, context); 112 lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`; 113 } 114 } 115 116 return 117 translateAggregate(context, cursor, "enum", nullable(enumName)) ~ 118 lines; 119 } 120 121 // not pure due to Cursor.opApply not being pure 122 string[] translateAggregate( 123 ref from!"dpp.runtime.context".Context context, 124 in from!"clang".Cursor cursor, 125 in string keyword, 126 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 127 ) 128 @safe 129 { 130 return translateAggregate(context, cursor, keyword, keyword, spelling); 131 } 132 133 134 // not pure due to Cursor.opApply not being pure 135 string[] translateAggregate( 136 ref from!"dpp.runtime.context".Context context, 137 in from!"clang".Cursor cursor, 138 in string cKeyword, 139 in string dKeyword, 140 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 141 ) 142 @safe 143 { 144 import dpp.translation.translation: translate; 145 import clang: Cursor, Type, AccessSpecifier; 146 import std.algorithm: map; 147 import std.array: array; 148 149 // remember all aggregate declarations 150 context.rememberAggregate(cursor); 151 152 const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get; 153 const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record 154 ? "static " ~ dKeyword 155 : dKeyword; 156 const firstLine = realDlangKeyword ~ ` ` ~ name; 157 158 if(!cursor.isDefinition) return [firstLine ~ `;`]; 159 160 string[] lines; 161 lines ~= firstLine; 162 lines ~= `{`; 163 164 // In C++ classes have private access as default 165 if(cKeyword == "class") { 166 lines ~= "private:"; 167 context.accessSpecifier = AccessSpecifier.Private; 168 } else 169 context.accessSpecifier = AccessSpecifier.Public; 170 171 BitFieldInfo bitFieldInfo; 172 173 lines ~= bitFieldInfo.header(cursor); 174 175 context.log("Children: ", cursor.children); 176 177 foreach(i, child; cursor.children) { 178 179 if(child.kind == Cursor.Kind.PackedAttr) { 180 lines ~= "align(1):"; 181 continue; 182 } 183 184 lines ~= bitFieldInfo.handle(child); 185 186 if(skipMember(child)) continue; 187 188 const childTranslation = () { 189 190 if(isPrivateField(child, context)) 191 return translatePrivateMember(child); 192 193 if(child.kind == Cursor.Kind.CXXBaseSpecifier) 194 return translateBase(i, child, context); 195 196 return translate(child, context); 197 }(); 198 199 lines ~= childTranslation.map!(a => " " ~ a).array; 200 201 // Possibly deal with C11 anonymous structs/unions. See issue #29. 202 lines ~= maybeC11AnonymousRecords(cursor, child, context); 203 204 bitFieldInfo.update(child); 205 } 206 207 lines ~= bitFieldInfo.finish; 208 lines ~= maybeOperators(cursor, name); 209 lines ~= maybeDisableDefaultCtor(cursor, dKeyword); 210 211 lines ~= `}`; 212 213 return lines; 214 } 215 216 private struct BitFieldInfo { 217 218 import dpp.runtime.context: Context; 219 import clang: Cursor; 220 221 /// if the last seen member was a bitfield 222 private bool lastMemberWasBitField; 223 /// the combined (summed) bitwidths of the bitfields members seen so far 224 private int totalBitWidth; 225 /// to generate new names 226 private int paddingNameIndex; 227 228 string[] header(in Cursor cursor) @safe nothrow { 229 import std.algorithm: any; 230 231 if(cursor.children.any!(a => a.isBitField)) { 232 // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}` 233 // would have sizeof 4, where as the corresponding bit fields in D would have 234 // size 1. So we correct here. See issue #7. 235 return [` import std.bitmanip: bitfields;`, ``, ` align(4):`]; 236 } else 237 return []; 238 239 } 240 241 string[] handle(in Cursor member) @safe pure nothrow { 242 243 string[] lines; 244 245 if(member.isBitField && !lastMemberWasBitField) 246 lines ~= ` mixin(bitfields!(`; 247 248 if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields; 249 250 if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) { 251 lines ~= finishBitFields; 252 lines ~= ` mixin(bitfields!(`; 253 } 254 255 return lines; 256 } 257 258 void update(in Cursor member) @safe pure nothrow { 259 lastMemberWasBitField = member.isBitField; 260 if(member.isBitField) totalBitWidth += member.bitWidth; 261 } 262 263 string[] finish() @safe pure nothrow { 264 return lastMemberWasBitField ? finishBitFields : []; 265 } 266 267 private string[] finishBitFields() @safe pure nothrow { 268 import std.conv: text; 269 270 271 int padding(in int totalBitWidth) { 272 273 for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) { 274 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth; 275 } 276 277 assert(0, text("Could not find powerOfTwo for width ", totalBitWidth)); 278 } 279 280 const paddingBits = padding(totalBitWidth); 281 282 string[] lines; 283 284 if(paddingBits) 285 lines ~= text(` uint, "`, newPaddingName, `", `, padding(totalBitWidth)); 286 287 lines ~= ` ));`; 288 289 totalBitWidth = 0; 290 291 return lines; 292 } 293 294 private string newPaddingName() @safe pure nothrow { 295 import std.conv: text; 296 return text("_padding_", paddingNameIndex++); 297 } 298 } 299 300 private bool isPrivateField(in from!"clang".Cursor cursor, 301 in from!"dpp.runtime.context".Context context) 302 @safe 303 { 304 import clang: Cursor, AccessSpecifier; 305 306 const isField = 307 cursor.kind == Cursor.Kind.FieldDecl || 308 cursor.kind == Cursor.Kind.VarDecl; 309 310 return 311 context.accessSpecifier == AccessSpecifier.Private 312 && isField 313 // The reason for this is templated types have negative sizes according 314 // to libclang, even if they're fully instantiated... 315 // So even though they're private, they can't be declared as opaque 316 // binary blobs because we don't know how large they are. 317 && cursor.type.getSizeof > 0 318 ; 319 320 } 321 322 // Since one can't access private members anyway, why bother translating? 323 // Declare an array of equivalent size in D, helps with the untranslatable 324 // parts of C++ 325 private string[] translatePrivateMember(in from!"clang".Cursor cursor) @safe { 326 import dpp.translation.type: translateOpaque; 327 328 return cursor.type.getSizeof > 0 329 ? [ translateOpaque(cursor.type) ~ ` ` ~ cursor.spelling ~ `;`] 330 : []; 331 } 332 333 334 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow { 335 import clang: Cursor; 336 return 337 !member.isDefinition 338 && member.kind != Cursor.Kind.CXXMethod 339 && member.kind != Cursor.Kind.Constructor 340 && member.kind != Cursor.Kind.Destructor 341 && member.kind != Cursor.Kind.VarDecl 342 && member.kind != Cursor.Kind.CXXBaseSpecifier 343 && member.kind != Cursor.Kind.ConversionFunction 344 && member.kind != Cursor.Kind.FunctionTemplate 345 ; 346 } 347 348 349 string[] translateField(in from!"clang".Cursor field, 350 ref from!"dpp.runtime.context".Context context) 351 @safe 352 { 353 354 import dpp.translation.dlang: maybeRename; 355 import dpp.translation.type: translate; 356 import clang: Cursor, Type; 357 import std.conv: text; 358 import std.typecons: No; 359 import std.array: replace; 360 361 assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field)); 362 363 // The field could be a pointer to an undeclared struct or a function pointer with parameter 364 // or return types that are a pointer to an undeclared struct. We have to remember these 365 // so as to be able to declare the structs for D consumption after the fact. 366 if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context); 367 368 // Remember the field name in case it ends up clashing with a type. 369 context.rememberField(field.spelling); 370 371 const type = translate(field.type, context, No.translatingFunction); 372 373 return field.isBitField 374 ? translateBitField(field, context, type) 375 : [text(type, " ", maybeRename(field, context), ";")]; 376 } 377 378 string[] translateBitField(in from!"clang".Cursor cursor, 379 ref from!"dpp.runtime.context".Context context, 380 in string type) 381 @safe 382 { 383 import dpp.translation.dlang: maybeRename; 384 import std.conv: text; 385 386 auto spelling = maybeRename(cursor, context); 387 // std.bitmanip.bitfield can't handle successive mixins with 388 // no name. See issue #35. 389 if(spelling == "") spelling = context.newAnonymousMemberName; 390 391 return [text(" ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)]; 392 } 393 394 // C allows elaborated types to appear in function parameters and member declarations 395 // if they're pointers and doesn't require a declaration for the referenced type. 396 private void maybeRememberStructsFromType(in from!"clang".Type type, 397 ref from!"dpp.runtime.context".Context context) 398 @safe 399 { 400 import clang: Type; 401 import std.range: only, chain; 402 403 const pointeeType = type.pointee.canonical; 404 const isFunction = 405 pointeeType.kind == Type.Kind.FunctionProto || 406 pointeeType.kind == Type.Kind.FunctionNoProto; 407 408 if(pointeeType.kind == Type.Kind.Record) 409 // can't use `only` with `const` for some reason 410 maybeRememberStructs([type], context); 411 else if(isFunction) 412 maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes), 413 context); 414 } 415 416 417 // C allows elaborated types to appear in function parameters and member declarations 418 // if they're pointers and doesn't require a declaration for the referenced type. 419 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context) 420 @safe if(isInputRange!R) 421 { 422 import dpp.translation.type: translate; 423 import clang: Type; 424 import std.algorithm: map, filter; 425 426 auto structTypes = types 427 .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record) 428 .map!(a => a.pointee.canonical); 429 430 void rememberStruct(in Type pointeeCanonicalType) { 431 import dpp.translation.type: translateElaborated; 432 import std.array: replace; 433 import std.algorithm: canFind; 434 435 // If it's not a C elaborated type, we don't need to do anything. 436 // The only reason this code exists is because elaborated types 437 // can be used in function signatures or member declarations 438 // without a declaration for the struct itself as long as it's 439 // a pointer. 440 if(!pointeeCanonicalType.spelling.canFind("struct ") && 441 !pointeeCanonicalType.spelling.canFind("union ") && 442 !pointeeCanonicalType.spelling.canFind("enum ")) 443 return; 444 445 const removeConst = pointeeCanonicalType.isConstQualified 446 ? pointeeCanonicalType.spelling.replace("const ", "") 447 : pointeeCanonicalType.spelling; 448 const removeVolatile = pointeeCanonicalType.isVolatileQualified 449 ? removeConst.replace("volatile ", "") 450 : removeConst; 451 452 context.rememberFieldStruct(translateElaborated(removeVolatile)); 453 } 454 455 foreach(structType; structTypes) 456 rememberStruct(structType); 457 } 458 459 // if the cursor is an aggregate in C, i.e. struct, union or enum 460 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow { 461 import clang: Cursor; 462 return 463 cursor.kind == Cursor.Kind.StructDecl || 464 cursor.kind == Cursor.Kind.UnionDecl || 465 cursor.kind == Cursor.Kind.EnumDecl; 466 } 467 468 469 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor, 470 in from!"clang".Cursor member, 471 ref from!"dpp.runtime.context".Context context) 472 @safe 473 474 { 475 import dpp.translation.type: translate, hasAnonymousSpelling; 476 import clang: Cursor, Type; 477 import std.algorithm: any, filter; 478 479 if(member.type.kind != Type.Kind.Record || member.spelling != "") return []; 480 481 // Either a field or an array of the type we expect 482 bool isFieldOfRightType(in Cursor member, in Cursor child) { 483 const isField = 484 child.kind == Cursor.Kind.FieldDecl && 485 child.type.canonical == member.type.canonical; 486 487 const isArrayOf = child.type.elementType.canonical == member.type.canonical; 488 return isField || isArrayOf; 489 } 490 491 // Check if the parent cursor has any fields have this type. 492 // If so, we don't need to declare a dummy variable. 493 const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a)); 494 if(anyFields) return []; 495 496 string[] lines; 497 const varName = context.newAnonymousMemberName; 498 499 //lines ~= " " ~ translate(member.type, context) ~ " " ~ varName ~ ";"; 500 const dtype = translate(member.type, context); 501 lines ~= " " ~ dtype ~ " " ~ varName ~ ";"; 502 503 foreach(subMember; member.children) { 504 if(subMember.kind == Cursor.Kind.FieldDecl) 505 lines ~= innerFieldAccessors(varName, subMember); 506 else if(subMember.type.canonical.kind == Type.Kind.Record && 507 hasAnonymousSpelling(subMember.type.canonical) && 508 !member.children.any!(a => isFieldOfRightType(subMember, a))) { 509 foreach(subSubMember; subMember) { 510 lines ~= " " ~ innerFieldAccessors(varName, subSubMember); 511 } 512 } 513 } 514 515 return lines; 516 } 517 518 519 // functions to emulate C11 anonymous structs/unions 520 private string[] innerFieldAccessors(in string varName, in from !"clang".Cursor subMember) @safe { 521 import std.format: format; 522 import std.algorithm: map; 523 import std.array: array; 524 525 string[] lines; 526 527 const fieldAccess = varName ~ "." ~ subMember.spelling; 528 const funcName = subMember.spelling; 529 530 lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }} 531 .format(funcName, fieldAccess); 532 533 lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }} 534 .format(funcName, fieldAccess); 535 536 return lines.map!(a => " " ~ a).array; 537 } 538 539 // emit a D opCmp if the cursor has operator<, operator> and operator== 540 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name) 541 @safe 542 { 543 import dpp.translation.function_: OPERATOR_PREFIX; 544 import std.algorithm: map, any; 545 import std.array: array, replace; 546 547 string[] lines; 548 549 bool hasOperator(in string op) { 550 return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op); 551 } 552 553 // if the aggregate has a parenthesis in its name it's because it's a templated 554 // struct/class, in which case the parameter has to have an exclamation mark. 555 const typeName = name.replace("(", "!("); 556 557 if(hasOperator(">") && hasOperator("<") && hasOperator("==")) { 558 lines ~= [ 559 `int opCmp()(` ~ typeName ~ ` other) const`, 560 `{`, 561 ` if(this.opCppLess(other)) return -1;`, 562 ` if(this.opCppMore(other)) return 1;`, 563 ` return 0;`, 564 `}`, 565 ].map!(a => ` ` ~ a).array; 566 } 567 568 if(hasOperator("!")) { 569 lines ~= [ 570 `bool opCast(T: bool)() const`, 571 `{`, 572 ` return !this.opCppBang();`, 573 `}`, 574 ].map!(a => ` ` ~ a).array; 575 } 576 577 return lines; 578 } 579 580 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword) 581 @safe 582 { 583 import dpp.translation.function_: numParams; 584 import clang: Cursor; 585 import std.algorithm: any; 586 587 bool hasNoArgsCtor(in Cursor child) { 588 return child.kind == Cursor.Kind.Constructor && 589 numParams(child) == 0; 590 } 591 592 if(dKeyword == "struct" && 593 cursor.children.any!hasNoArgsCtor) { 594 return [` @disable this();`]; 595 } 596 597 return []; 598 } 599 600 601 private string[] translateBase(size_t index, 602 in from!"clang".Cursor cursor, 603 ref from!"dpp.runtime.context".Context context) 604 @safe 605 in(cursor.kind == from!"clang".Cursor.Kind.CXXBaseSpecifier) 606 do 607 { 608 import dpp.translation.type: translate; 609 import std.typecons: No; 610 import std.algorithm: canFind; 611 import std.conv: text; 612 613 const type = translate(cursor.type, context, No.translatingFunction); 614 615 // FIXME - see it.cpp.templates.__or_ 616 // Not only would that test fail if this weren't here, but the spelling of 617 // the type parameters would be completely wrong as well. 618 if(type.canFind("...")) return []; 619 620 // FIXME - type traits failures due to inheritance 621 if(type.canFind("&")) return []; 622 623 const fieldName = text("_base", index); 624 auto fieldDecl = type ~ " " ~ fieldName ~ ";"; 625 auto maybeAliasThis = index == 0 626 ? [`alias ` ~ fieldName ~ ` this;`] 627 : []; 628 629 return fieldDecl ~ maybeAliasThis; 630 }