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 dpp.translation.template_: templateSpelling, translateTemplateParams, 34 translateSpecialisedTemplateParams; 35 import clang: Cursor; 36 import std.typecons: Nullable, nullable; 37 import std.array: join; 38 import std.conv: text; 39 40 assert( 41 cursor.kind == Cursor.Kind.StructDecl || 42 cursor.kind == Cursor.Kind.ClassDecl || 43 cursor.kind == Cursor.Kind.ClassTemplate || 44 cursor.kind == Cursor.Kind.ClassTemplatePartialSpecialization 45 ); 46 47 const spelling = () { 48 49 // full template 50 if(cursor.kind == Cursor.Kind.ClassTemplate) 51 return nullable(templateSpelling(cursor, translateTemplateParams(cursor, context))); 52 53 // partial or full template specialisation 54 if(cursor.type.numTemplateArguments != -1) 55 return nullable(templateSpelling(cursor, translateSpecialisedTemplateParams(cursor, context))); 56 57 // non-template class/struct 58 return Nullable!string(); 59 }(); 60 61 const dKeyword = "struct"; 62 63 return translateAggregate(context, cursor, cKeyword, dKeyword, spelling); 64 } 65 66 67 68 69 70 string[] translateUnion(in from!"clang".Cursor cursor, 71 ref from!"dpp.runtime.context".Context context) 72 @safe 73 { 74 import clang: Cursor; 75 assert(cursor.kind == Cursor.Kind.UnionDecl); 76 return translateAggregate(context, cursor, "union"); 77 } 78 79 string[] translateEnum(in from!"clang".Cursor cursor, 80 ref from!"dpp.runtime.context".Context context) 81 @safe 82 { 83 import clang: Cursor; 84 import std.typecons: nullable; 85 86 assert(cursor.kind == Cursor.Kind.EnumDecl); 87 88 // Translate it twice so that C semantics are the same (global names) 89 // but also have a named version for optional type correctness and 90 // reflection capabilities. 91 // This means that `enum Foo { foo, bar }` in C will become: 92 // `enum Foo { foo, bar }` _and_ 93 // `enum foo = Foo.foo; enum bar = Foo.bar;` in D. 94 95 auto enumName = context.spellingOrNickname(cursor); 96 97 string[] lines; 98 foreach(member; cursor) { 99 if(!member.isDefinition) continue; 100 auto memName = member.spelling; 101 lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`; 102 } 103 104 return 105 translateAggregate(context, cursor, "enum", nullable(enumName)) ~ 106 lines; 107 } 108 109 // not pure due to Cursor.opApply not being pure 110 string[] translateAggregate( 111 ref from!"dpp.runtime.context".Context context, 112 in from!"clang".Cursor cursor, 113 in string keyword, 114 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 115 ) 116 @safe 117 { 118 return translateAggregate(context, cursor, keyword, keyword, spelling); 119 } 120 121 private struct BitFieldInfo { 122 123 import dpp.runtime.context: Context; 124 import clang: Cursor; 125 126 /// if the last seen member was a bitfield 127 private bool lastMemberWasBitField; 128 /// the combined (summed) bitwidths of the bitfields members seen so far 129 private int totalBitWidth; 130 /// to generate new names 131 private int paddingNameIndex; 132 133 string[] header(in Cursor cursor) @safe nothrow { 134 import std.algorithm: any; 135 136 if(cursor.children.any!(a => a.isBitField)) { 137 // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}` 138 // would have sizeof 4, where as the corresponding bit fields in D would have 139 // size 1. So we correct here. See issue #7. 140 return [` import std.bitmanip: bitfields;`, ``, ` align(4):`]; 141 } else 142 return []; 143 144 } 145 146 string[] handle(in Cursor member) @safe pure nothrow { 147 148 string[] lines; 149 150 if(member.isBitField && !lastMemberWasBitField) 151 lines ~= ` mixin(bitfields!(`; 152 153 if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields; 154 155 if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) { 156 lines ~= finishBitFields; 157 lines ~= ` mixin(bitfields!(`; 158 } 159 160 return lines; 161 } 162 163 void update(in Cursor member) @safe pure nothrow { 164 lastMemberWasBitField = member.isBitField; 165 if(member.isBitField) totalBitWidth += member.bitWidth; 166 } 167 168 string[] finish() @safe pure nothrow { 169 return lastMemberWasBitField ? finishBitFields : []; 170 } 171 172 private string[] finishBitFields() @safe pure nothrow { 173 import std.conv: text; 174 175 176 int padding(in int totalBitWidth) { 177 178 for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) { 179 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth; 180 } 181 182 assert(0, text("Could not find powerOfTwo for width ", totalBitWidth)); 183 } 184 185 const paddingBits = padding(totalBitWidth); 186 187 string[] lines; 188 189 if(paddingBits) 190 lines ~= text(` uint, "`, newPaddingName, `", `, padding(totalBitWidth)); 191 192 lines ~= ` ));`; 193 194 totalBitWidth = 0; 195 196 return lines; 197 } 198 199 private string newPaddingName() @safe pure nothrow { 200 import std.conv: text; 201 return text("_padding_", paddingNameIndex++); 202 } 203 204 } 205 206 // not pure due to Cursor.opApply not being pure 207 string[] translateAggregate( 208 ref from!"dpp.runtime.context".Context context, 209 in from!"clang".Cursor cursor, 210 in string cKeyword, 211 in string dKeyword, 212 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 213 ) 214 @safe 215 { 216 import dpp.translation.translation: translate; 217 import clang: Cursor, Type; 218 import std.algorithm: map; 219 import std.array: array; 220 221 // remember all aggregate declarations 222 context.rememberAggregate(cursor); 223 224 const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get; 225 const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record 226 ? "static " ~ dKeyword 227 : dKeyword; 228 const firstLine = realDlangKeyword ~ ` ` ~ name; 229 230 if(!cursor.isDefinition) return [firstLine ~ `;`]; 231 232 string[] lines; 233 lines ~= firstLine; 234 lines ~= `{`; 235 236 if(cKeyword == "class") lines ~= "private:"; 237 238 BitFieldInfo bitFieldInfo; 239 240 lines ~= bitFieldInfo.header(cursor); 241 242 context.log("Children: ", cursor.children); 243 244 foreach(member; cursor.children) { 245 246 if(member.kind == Cursor.Kind.PackedAttr) { 247 lines ~= "align(1):"; 248 continue; 249 } 250 251 lines ~= bitFieldInfo.handle(member); 252 253 if(skipMember(member)) continue; 254 255 lines ~= translate(member, context).map!(a => " " ~ a).array; 256 257 // Possibly deal with C11 anonymous structs/unions. See issue #29. 258 lines ~= maybeC11AnonymousRecords(cursor, member, context); 259 260 bitFieldInfo.update(member); 261 } 262 263 lines ~= bitFieldInfo.finish; 264 lines ~= maybeOperators(cursor, name); 265 lines ~= maybeDisableDefaultCtor(cursor, dKeyword); 266 267 lines ~= `}`; 268 269 return lines; 270 } 271 272 273 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow { 274 import clang: Cursor; 275 return 276 !member.isDefinition 277 && member.kind != Cursor.Kind.CXXMethod 278 && member.kind != Cursor.Kind.Constructor 279 && member.kind != Cursor.Kind.Destructor 280 && member.kind != Cursor.Kind.VarDecl 281 && member.kind != Cursor.Kind.CXXBaseSpecifier 282 && member.kind != Cursor.Kind.ConversionFunction 283 ; 284 } 285 286 287 string[] translateField(in from!"clang".Cursor field, 288 ref from!"dpp.runtime.context".Context context) 289 @safe 290 { 291 292 import dpp.translation.dlang: maybeRename; 293 import dpp.translation.type: translate; 294 import clang: Cursor, Type; 295 import std.conv: text; 296 import std.typecons: No; 297 import std.array: replace; 298 299 assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field)); 300 301 // The field could be a pointer to an undeclared struct or a function pointer with parameter 302 // or return types that are a pointer to an undeclared struct. We have to remember these 303 // so as to be able to declare the structs for D consumption after the fact. 304 if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context); 305 306 // Remember the field name in case it ends up clashing with a type. 307 context.rememberField(field.spelling); 308 309 const type = translate(field.type, context, No.translatingFunction); 310 311 return field.isBitField 312 ? translateBitField(field, context, type) 313 : [text(type, " ", maybeRename(field, context), ";")]; 314 } 315 316 string[] translateBitField(in from!"clang".Cursor cursor, 317 ref from!"dpp.runtime.context".Context context, 318 in string type) 319 @safe 320 { 321 import dpp.translation.dlang: maybeRename; 322 import std.conv: text; 323 324 auto spelling = maybeRename(cursor, context); 325 // std.bitmanip.bitfield can't handle successive mixins with 326 // no name. See issue #35. 327 if(spelling == "") spelling = context.newAnonymousMemberName; 328 329 return [text(" ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)]; 330 } 331 332 private void maybeRememberStructsFromType(in from!"clang".Type type, 333 ref from!"dpp.runtime.context".Context context) 334 @safe pure 335 { 336 import clang: Type; 337 import std.range: only, chain; 338 339 const pointeeType = type.pointee.canonical; 340 const isFunction = 341 pointeeType.kind == Type.Kind.FunctionProto || 342 pointeeType.kind == Type.Kind.FunctionNoProto; 343 344 if(pointeeType.kind == Type.Kind.Record) 345 maybeRememberStructs([type], context); 346 else if(isFunction) 347 maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes), 348 context); 349 } 350 351 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context) 352 @safe pure if(isInputRange!R) 353 { 354 import dpp.translation.type: translate; 355 import clang: Type; 356 import std.algorithm: map, filter; 357 358 auto structTypes = types 359 .filter!(a => a.kind == Type.Kind.Pointer && a.pointee.canonical.kind == Type.Kind.Record) 360 .map!(a => a.pointee.canonical); 361 362 void rememberStruct(in Type pointeeCanonicalType) { 363 const translatedType = translate(pointeeCanonicalType, context); 364 // const becomes a problem if we have to define a struct at the end of all translations. 365 // See it.compile.projects.nv_alloc_ops 366 enum constPrefix = "const("; 367 const cleanedType = pointeeCanonicalType.isConstQualified 368 ? translatedType[constPrefix.length .. $-1] // unpack from const(T) 369 : translatedType; 370 371 if(cleanedType != "va_list") 372 context.rememberFieldStruct(cleanedType); 373 } 374 375 foreach(structType; structTypes) 376 rememberStruct(structType); 377 } 378 379 // if the cursor is an aggregate in C, i.e. struct, union or enum 380 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow { 381 import clang: Cursor; 382 return 383 cursor.kind == Cursor.Kind.StructDecl || 384 cursor.kind == Cursor.Kind.UnionDecl || 385 cursor.kind == Cursor.Kind.EnumDecl; 386 } 387 388 389 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor, 390 in from!"clang".Cursor member, 391 ref from!"dpp.runtime.context".Context context) 392 @safe 393 394 { 395 import dpp.translation.type: translate, hasAnonymousSpelling; 396 import clang: Cursor, Type; 397 import std.algorithm: any, filter; 398 399 if(member.type.kind != Type.Kind.Record || member.spelling != "") return []; 400 401 // Either a field or an array of the type we expect 402 bool isFieldOfRightType(in Cursor member, in Cursor child) { 403 const isField = 404 child.kind == Cursor.Kind.FieldDecl && 405 child.type.canonical == member.type.canonical; 406 407 const isArrayOf = child.type.elementType.canonical == member.type.canonical; 408 return isField || isArrayOf; 409 } 410 411 // Check if the parent cursor has any fields have this type. 412 // If so, we don't need to declare a dummy variable. 413 const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a)); 414 if(anyFields) return []; 415 416 string[] lines; 417 const varName = context.newAnonymousMemberName; 418 419 //lines ~= " " ~ translate(member.type, context) ~ " " ~ varName ~ ";"; 420 const dtype = translate(member.type, context); 421 lines ~= " " ~ dtype ~ " " ~ varName ~ ";"; 422 423 foreach(subMember; member.children) { 424 if(subMember.kind == Cursor.Kind.FieldDecl) 425 lines ~= innerFieldAccessors(varName, subMember); 426 else if(subMember.type.canonical.kind == Type.Kind.Record && 427 hasAnonymousSpelling(subMember.type.canonical) && 428 !member.children.any!(a => isFieldOfRightType(subMember, a))) { 429 foreach(subSubMember; subMember) { 430 lines ~= " " ~ innerFieldAccessors(varName, subSubMember); 431 } 432 } 433 } 434 435 return lines; 436 } 437 438 439 // functions to emulate C11 anonymous structs/unions 440 private string[] innerFieldAccessors(in string varName, in from !"clang".Cursor subMember) @safe { 441 import std.format: format; 442 import std.algorithm: map; 443 import std.array: array; 444 445 string[] lines; 446 447 const fieldAccess = varName ~ "." ~ subMember.spelling; 448 const funcName = subMember.spelling; 449 450 lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }} 451 .format(funcName, fieldAccess); 452 453 lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }} 454 .format(funcName, fieldAccess); 455 456 return lines.map!(a => " " ~ a).array; 457 } 458 459 // emit a D opCmp if the cursor has operator<, operator> and operator== 460 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name) 461 @safe 462 { 463 import dpp.translation.function_: OPERATOR_PREFIX; 464 import std.algorithm: map, any; 465 import std.array: array; 466 467 string[] lines; 468 469 bool hasOperator(in string op) { 470 return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op); 471 } 472 473 if(hasOperator(">") && hasOperator("<") && hasOperator("==")) { 474 lines ~= [ 475 `int opCmp()(` ~ name ~ ` other) const`, 476 `{`, 477 ` if(this.opCppLess(other)) return -1;`, 478 ` if(this.opCppMore(other)) return 1;`, 479 ` return 0;`, 480 `}`, 481 ].map!(a => ` ` ~ a).array; 482 } 483 484 if(hasOperator("!")) { 485 lines ~= [ 486 `bool opCast(T: bool)() const`, 487 `{`, 488 ` return !this.opCppBang();`, 489 `}`, 490 ].map!(a => ` ` ~ a).array; 491 } 492 493 return lines; 494 } 495 496 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword) 497 @safe 498 { 499 import clang: Cursor; 500 import std.algorithm: any; 501 502 if(dKeyword == "struct" && 503 cursor.children.any!(a => a.kind == Cursor.Kind.Constructor)) { 504 return [` @disable this();`]; 505 } 506 507 return []; 508 }