1 /** 2 Function translations. 3 */ 4 module dpp.translation.function_; 5 6 import dpp.from; 7 8 9 enum OPERATOR_PREFIX = "operator"; 10 11 12 string[] translateFunction(in from!"clang".Cursor cursor, 13 ref from!"dpp.runtime.context".Context context) 14 @safe 15 in( 16 cursor.kind == from!"clang".Cursor.Kind.FunctionDecl || 17 cursor.kind == from!"clang".Cursor.Kind.CXXMethod || 18 cursor.kind == from!"clang".Cursor.Kind.Constructor || 19 cursor.kind == from!"clang".Cursor.Kind.Destructor || 20 cursor.kind == from!"clang".Cursor.Kind.ConversionFunction || 21 cursor.kind == from!"clang".Cursor.Kind.FunctionTemplate 22 ) 23 do 24 { 25 import dpp.translation.dlang: maybePragma; 26 import dpp.translation.aggregate: maybeRememberStructs; 27 28 if(ignoreFunction(cursor)) return []; 29 30 // FIXME - stop special casing the move ctor 31 auto moveCtorLines = maybeMoveCtor(cursor, context); 32 if(moveCtorLines.length) return moveCtorLines; 33 34 string[] lines; 35 36 lines ~= maybeCopyCtor(cursor, context); 37 lines ~= maybeOperator(cursor, context); 38 39 // never declared types might lurk here 40 maybeRememberStructs(cursor.type.paramTypes, context); 41 42 const spelling = functionSpelling(cursor, context); 43 44 lines ~= [ 45 maybePragma(cursor, context) ~ functionDecl(cursor, context, spelling) 46 ]; 47 48 context.log(""); 49 50 return lines; 51 } 52 53 private bool ignoreFunction(in from!"clang".Cursor cursor) @safe { 54 import dpp.translation.aggregate: dKeywordFromStrass; 55 import clang: Cursor, Type, Token; 56 import std.algorithm: countUntil, any, canFind, startsWith; 57 58 // C++ partial specialisation function bodies 59 if(cursor.semanticParent.kind == Cursor.Kind.ClassTemplatePartialSpecialization) 60 return true; 61 62 // C++ deleted functions 63 const deleteIndex = cursor.tokens.countUntil(Token(Token.Kind.Keyword, "delete")); 64 if(deleteIndex != -1 && deleteIndex > 1) { 65 if(cursor.tokens[deleteIndex - 1] == Token(Token.Kind.Punctuation, "=")) 66 return true; 67 } 68 69 // C++ member functions defined "outside the class", e.g. 70 // `int Foo::bar() const { return 42; }` 71 // This first condition checks if the function cursor has a body (compound statement) 72 if(cursor.children.any!(a => a.kind == Cursor.Kind.CompoundStmt)) { 73 74 // If it has a body, we check that its tokens contain "::" in the right place 75 76 const tokens = cursor.tokens; 77 const doubleColonIndex = tokens.countUntil(Token(Token.Kind.Punctuation, "::")); 78 79 if(doubleColonIndex != -1) { 80 const nextToken = tokens[doubleColonIndex + 1]; 81 // The reason we're not checking the next token's spelling exactly is 82 // because for templated types the cursor's spelling might be `Foo<T>` 83 // but the token is `Foo`. 84 if(nextToken.kind == Token.Kind.Identifier && 85 cursor.spelling.startsWith(nextToken.spelling)) 86 return true; 87 } 88 } 89 90 // No default contructors for structs in D 91 if( 92 cursor.kind == Cursor.Kind.Constructor 93 && numParams(cursor) == 0 94 && dKeywordFromStrass(cursor.semanticParent) == "struct" 95 ) 96 return true; 97 98 return false; 99 } 100 101 private string functionDecl( 102 in from!"clang".Cursor cursor, 103 ref from!"dpp.runtime.context".Context context, 104 in string spelling, 105 in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names 106 ) 107 @safe 108 { 109 import dpp.translation.template_: translateTemplateParams; 110 import dpp.translation.exception: UntranslatableException; 111 import dpp.clang: isOverride; 112 import std.conv: text; 113 import std.algorithm: endsWith, canFind; 114 import std.array: join; 115 116 context.log("Function return type (raw): ", cursor.type.returnType); 117 context.log("Function children: ", cursor.children); 118 context.log("Function paramTypes: ", cursor.type.paramTypes); 119 const returnType = returnType(cursor, context); 120 context.log("Function return type (translated): ", returnType); 121 122 const params = translateAllParamTypes(cursor, context, names).join(", "); 123 context.log("Translated parameters: '", params, "'"); 124 // const C++ method? 125 const const_ = cursor.isConstCppMethod ? " const" : ""; 126 127 auto templateParams = translateTemplateParams(cursor, context); 128 const ctParams = templateParams.empty 129 ? "" 130 : "(" ~ templateParams.join(", ") ~ ")" 131 ; 132 133 // FIXME: avoid opBinary(string op: )(CT params)(RT params) 134 // See it.cpp.function_.opBinary 135 if(ctParams != "" && spelling.canFind("(")) 136 throw new UntranslatableException("BUG with templated operators"); 137 138 string prefix() { 139 if(cursor.isPureVirtual) 140 return "abstract "; 141 else if(!cursor.isVirtual) 142 return "final "; 143 else if(cursor.isOverride) 144 return "override "; 145 else 146 return ""; 147 } 148 149 return text(prefix, returnType, " ", spelling, ctParams, "(", params, ") @nogc nothrow", const_, ";"); 150 } 151 152 private string returnType(in from!"clang".Cursor cursor, 153 ref from!"dpp.runtime.context".Context context) 154 @safe 155 { 156 import dpp.translation.type: translate; 157 import clang: Cursor; 158 import std.typecons: Yes; 159 160 const blob = blob(cursor.returnType, context); 161 if(blob != "") return blob; 162 163 const indentation = context.indentation; 164 165 const isCtorOrDtor = isConstructor(cursor) || cursor.kind == Cursor.Kind.Destructor; 166 const dType = isCtorOrDtor 167 ? "" 168 : translate(cursor.returnType, context, Yes.translatingFunction); 169 170 context.setIndentation(indentation); 171 172 const maybeStatic = cursor.storageClass == Cursor.StorageClass.Static ? "static " : ""; 173 174 return maybeStatic ~ dType; 175 } 176 177 private string[] maybeOperator(in from!"clang".Cursor cursor, 178 ref from!"dpp.runtime.context".Context context) 179 @safe 180 { 181 import std.algorithm: map; 182 import std.array: join, array; 183 import std.typecons: Yes; 184 import std.range: iota; 185 import std.conv: text; 186 187 if(!isSupportedOperatorInD(cursor)) return []; 188 189 const params = translateAllParamTypes(cursor, context).array; 190 191 return [ 192 // remove semicolon from the end with [0..$-1] 193 `extern(D) ` ~ functionDecl(cursor, context, operatorSpellingD(cursor, context), Yes.names)[0..$-1], 194 `{`, 195 ` return ` ~ operatorSpellingCpp(cursor, context) ~ `(` ~ params.length.iota.map!(a => text("arg", a)).join(", ") ~ `);`, 196 `}`, 197 ]; 198 } 199 200 private bool isSupportedOperatorInD(in from!"clang".Cursor cursor) @safe nothrow { 201 import dpp.translation.aggregate: dKeywordFromStrass; 202 import clang: Cursor; 203 import std.algorithm: map, canFind; 204 205 if(!isOperator(cursor)) return false; 206 207 // No D support for free function operator overloads 208 if(cursor.semanticParent.kind == Cursor.Kind.TranslationUnit) return false; 209 210 const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 211 212 // FIXME - should only check for identity assignment, 213 // not all assignment operators 214 if(dKeywordFromStrass(cursor.semanticParent) == "class" && cppOperator == "=") 215 return false; 216 217 const unsupportedSpellings = [`!`, `,`, `&&`, `||`, `->`, `->*`]; 218 if(unsupportedSpellings.canFind(cppOperator)) return false; 219 220 if(isUnaryOperator(cursor) && cppOperator == "&") return false; 221 if(!isUnaryOperator(cursor) && !isBinaryOperator(cursor)) return false; 222 223 return true; 224 } 225 226 private bool isOperator(in from!"clang".Cursor cursor) @safe pure nothrow { 227 import std.algorithm: startsWith; 228 return 229 cursor.spelling.startsWith(OPERATOR_PREFIX) 230 && cursor.spelling.length > OPERATOR_PREFIX.length 231 && cursor.spelling[OPERATOR_PREFIX.length] != '_' 232 ; 233 } 234 235 236 private string functionSpelling(in from!"clang".Cursor cursor, 237 ref from!"dpp.runtime.context".Context context) 238 @safe 239 { 240 import clang: Cursor; 241 242 if(isConstructor(cursor)) return "this"; 243 if(cursor.kind == Cursor.Kind.Destructor) return "~this"; 244 245 if(isOperator(cursor)) return operatorSpellingCpp(cursor, context); 246 247 // if no special case 248 return context.rememberLinkable(cursor); 249 } 250 251 private bool isConstructor(in from!"clang".Cursor cursor) @safe nothrow { 252 import clang: Cursor; 253 import std.algorithm: startsWith; 254 255 return cursor.kind == Cursor.Kind.Constructor || 256 cursor.spelling.startsWith(cursor.semanticParent.spelling ~ "<"); 257 } 258 259 private string operatorSpellingD(in from!"clang".Cursor cursor, 260 ref from!"dpp.runtime.context".Context context) 261 @safe 262 { 263 import clang: Cursor; 264 import std.range: walkLength; 265 import std.algorithm: canFind; 266 import std.conv: text; 267 268 const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 269 270 if(cursor.kind == Cursor.Kind.ConversionFunction) { 271 return `opCast(T: ` ~ returnType(cursor, context) ~ `)`; 272 } 273 274 if(cppOperator.length > 1 && 275 cppOperator[$-1] == '=' && 276 (cppOperator.length != 2 || !['=', '!', '<', '>'].canFind(cppOperator[0]))) 277 return `opOpAssign(string op: "` ~ cppOperator[0 .. $-1] ~ `")`; 278 279 assert(isUnaryOperator(cursor) || isBinaryOperator(cursor), 280 text("Cursor is neither a unary or binary operator: ", cursor, "@", cursor.sourceRange.start)); 281 const dFunction = isBinaryOperator(cursor) ? "opBinary" : "opUnary"; 282 283 // to avoid problems, some C++ operators are translated as template functions, 284 // but as seen in #114, don't do this if they're already templates! 285 const templateParens = cursor.templateParams.length 286 ? "" 287 : "()"; 288 289 // Some of the operators here have empty parentheses around them. This is to 290 // to make them templates and only be instantiated if needed. See #102. 291 switch(cppOperator) { 292 default: 293 return dFunction ~ `(string op: "` ~ cppOperator ~ `")`; 294 295 case "=": return `opAssign` ~ templateParens; 296 case "()": return `opCall` ~ templateParens; 297 case "[]": return `opIndex` ~ templateParens; 298 case "==": return `opEquals` ~ templateParens; 299 } 300 } 301 302 private bool isUnaryOperator(in from!"clang".Cursor cursor) @safe pure nothrow { 303 return isOperator(cursor) && numParams(cursor) == 0; 304 } 305 306 private bool isBinaryOperator(in from!"clang".Cursor cursor) @safe pure nothrow { 307 return isOperator(cursor) && numParams(cursor) == 1; 308 } 309 310 package long numParams(in from!"clang".Cursor cursor) @safe pure nothrow { 311 import std.range: walkLength; 312 return walkLength(cursor.type.paramTypes); 313 } 314 315 private string operatorSpellingCpp(in from!"clang".Cursor cursor, 316 ref from!"dpp.runtime.context".Context context) 317 @safe 318 in(isOperator(cursor)) 319 do 320 { 321 import dpp.translation.type: translate; 322 import dpp.translation.exception: UntranslatableException; 323 import clang: Cursor; 324 import std..string: replace; 325 import std.algorithm: startsWith; 326 327 const operator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 328 329 if(cursor.kind == Cursor.Kind.ConversionFunction) { 330 return "opCppCast_" ~ translate(cursor.returnType, context).replace(".", "_"); 331 } 332 333 switch(operator) { 334 default: 335 if(operator.startsWith(`""`)) // user-defined string literal 336 throw new UntranslatableException("Cannot translate user-defined literals"); 337 338 throw new UntranslatableException("Unknown C++ spelling for operator '" ~ operator ~ "'"); 339 340 case "+": return `opCppPlus`; 341 case "-": return `opCppMinus`; 342 case "++": return `opCppIncrement`; 343 case "--": return `opCppDecrement`; 344 case "*": return `opCppMul`; 345 case "/": return `opCppDiv`; 346 case "&": return `opCppAmpersand`; 347 case "~": return `opCppTilde`; 348 case "%": return `opCppMod`; 349 case "^": return `opCppCaret`; 350 case "|": return `opCppPipe`; 351 case "=": return `opCppAssign`; 352 case ">>": return `opCppRShift`; 353 case "<<": return `opCppLShift`; 354 case "->": return `opCppArrow`; 355 case "!": return `opCppBang`; 356 case "&&": return `opCppAnd`; 357 case "||": return `opCppOr`; 358 case ",": return `opCppComma`; 359 case "->*": return `opCppArrowStar`; 360 case "+=": return `opCppPlusAssign`; 361 case "-=": return `opCppMinusAssign`; 362 case "*=": return `opCppMulAssign`; 363 case "/=": return `opCppDivAssign`; 364 case "%=": return `opCppModAssign`; 365 case "^=": return `opCppCaretAssign`; 366 case "&=": return `opCppAmpersandAssign`; 367 case "|=": return `opCppPipeAssign`; 368 case ">>=": return `opCppRShiftAssign`; 369 case "<<=": return `opCppLShiftAssign`; 370 case "()": return `opCppCall`; 371 case "[]": return `opCppIndex`; 372 case "==": return `opCppEquals`; 373 case "!=": return `opCppNotEquals`; 374 case "<=": return `opCppLessEquals`; 375 case ">=": return `opCppMoreEquals`; 376 case "<": return `opCppLess`; 377 case ">": return `opCppMore`; 378 case " new": return `opCppNew`; 379 case " new[]": return `opCppNewArray`; 380 case " delete": return `opCppDelete`; 381 case " delete[]": return `opCppDeleteArray`; 382 } 383 384 assert(0); 385 } 386 387 388 // Add a non-const ref that forwards to the const ref copy ctor 389 // so that lvalues don't match the by-value ctor 390 private string[] maybeCopyCtor(in from!"clang".Cursor cursor, 391 ref from!"dpp.runtime.context".Context context) 392 @safe 393 { 394 395 import dpp.translation.dlang: maybeRename, maybePragma; 396 import dpp.translation.type: translate; 397 import clang: Cursor, Type; 398 import std.array: front; 399 400 if(!cursor.isCopyConstructor) return []; 401 402 const param = cursor.type.paramTypes.front; 403 const translated = translateFunctionParam(cursor, param, context); 404 const dType = translated["ref const(".length .. $ - 1]; // remove the constness 405 406 return [ 407 `this(ref ` ~ dType ~ ` other)`, 408 `{`, 409 ` this(*cast(const ` ~ dType ~ `*) &other);`, 410 `}`, 411 ]; 412 } 413 414 415 private string[] maybeMoveCtor(in from!"clang".Cursor cursor, 416 ref from!"dpp.runtime.context".Context context) 417 @safe 418 { 419 import dpp.translation.dlang: maybeRename, maybePragma; 420 import dpp.translation.type: translate; 421 import clang: Cursor, Type; 422 import std.array: front; 423 424 if(!cursor.isMoveConstructor) return []; 425 426 const paramType = cursor.type.paramTypes.front; 427 const pointee = translate(paramType.pointee, context); 428 429 return [ 430 // The actual C++ move constructor but declared to take a pointer 431 maybePragma(cursor, context) ~ " this(" ~ pointee ~ "*);", 432 // The fake D move constructor 433 "this(" ~ translate(paramType, context) ~ " wrapper) {", 434 " this(wrapper.ptr);", 435 // Hollow out moved-from value 436 " static if(is(typeof( { typeof(*wrapper.ptr) _; } )))", 437 " {", 438 " typeof(*wrapper.ptr) init;", 439 " *wrapper.ptr = init;", 440 " }", 441 " else", 442 " {", 443 " import core.stdc.string: memset;", 444 " memset(wrapper.ptr, 0, typeof(*wrapper.ptr).sizeof);", 445 " }", 446 "}", 447 // The fake D by-value constructor imitating C++ semantics 448 "this(" ~ pointee ~ " other)", 449 "{", 450 // Forward the call to the C++ move constructor 451 " this(&other);", 452 "}", 453 ]; 454 } 455 456 // includes C variadic params 457 private auto translateAllParamTypes( 458 in from!"clang".Cursor cursor, 459 ref from!"dpp.runtime.context".Context context, 460 in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names, 461 ) 462 @safe 463 { 464 import clang: Cursor; 465 import std.algorithm: endsWith, map; 466 import std.range: enumerate, chain; 467 import std.conv: text; 468 469 // Here we used to check that if there were no parameters and the language is C, 470 // then the correct translation in D would be (...); 471 // However, that's not allowed in D. It just so happens that C production code 472 // exists that doesn't bother with (void), so instead of producing something that 473 // doesn't compile, we compromise and assume the user meant (void) 474 475 auto paramTypes = translateParamTypes(cursor, cursor.type, context); 476 477 const isVariadic = 478 cursor.type.spelling.endsWith("...)") 479 && cursor.kind != Cursor.Kind.FunctionTemplate 480 ; 481 const variadicParams = isVariadic ? ["..."] : []; 482 483 return enumerate(chain(paramTypes, variadicParams)) 484 .map!(a => names ? a[1] ~ text(" arg", a[0]) : a[1]) 485 ; 486 } 487 488 // does not include C variadic params 489 auto translateParamTypes(in from!"clang".Cursor cursor, 490 in from!"clang".Type cursorType, 491 ref from!"dpp.runtime.context".Context context) 492 @safe 493 { 494 import std.algorithm: map; 495 import std.range: tee, enumerate; 496 497 return cursorType 498 .paramTypes 499 .enumerate 500 .tee!((a) { 501 context.log(" Function param #", a[0], 502 " type: ", a[1], " canonical ", a[1].canonical); 503 }) 504 .map!(t => translateFunctionParam(cursor, t[1], context)) 505 ; 506 } 507 508 509 // translate a ParmDecl 510 private string translateFunctionParam(in from!"clang".Cursor function_, 511 in from!"clang".Type paramType, 512 ref from!"dpp.runtime.context".Context context) 513 @safe 514 { 515 516 import dpp.translation.type: translate; 517 import clang: Type, Language; 518 import std.typecons: Yes; 519 import std.array: replace; 520 521 // Could be an opaque type 522 const blob = blob(paramType, context); 523 if(blob != "") return blob; 524 525 // See #43 526 const(Type) deunexpose(in Type type) { 527 return type.kind == Type.Kind.Unexposed && function_.language != Language.CPlusPlus 528 ? type.canonical 529 : type; 530 } 531 532 // See contract.ctor.copy.definition.declartion for libclang silliness leading to this. 533 // If the enclosing struct/class is templated and the function isn't, then we might 534 // get "type-parameter-0-0" spellings even when the actual name is e.g. `T`. 535 const numAggTemplateParams = function_.semanticParent.templateParams.length; 536 const numFunTemplateParams = function_.templateParams.length; 537 538 // HACK 539 // FIXME: not sure what to do if the numbers aren't exactly 1 and 0 540 const useAggTemplateParamSpelling = numAggTemplateParams == 1 && numFunTemplateParams == 0; 541 const aggTemplateParamSpelling = useAggTemplateParamSpelling 542 ? function_.semanticParent.templateParams[0].spelling 543 : ""; 544 const translation = translate(deunexpose(paramType), context, Yes.translatingFunction); 545 546 return useAggTemplateParamSpelling 547 ? translation.replace("type_parameter_0_0", aggTemplateParamSpelling) 548 : translation; 549 } 550 551 552 private string blob(in from!"clang".Type type, 553 in from!"dpp.runtime.context".Context context) 554 @safe 555 { 556 import dpp.translation.type: translateOpaque; 557 import clang: Type; 558 import std.conv: text; 559 560 // if the type is from an ignored namespace, use an opaque type, but not 561 // if it's a pointer or reference - in that case the user can always 562 // declare the type with no definition. 563 if(context.isFromIgnoredNs(type) && 564 type.kind != Type.Kind.LValueReference && 565 type.kind != Type.Kind.Pointer) 566 { 567 return translateOpaque(type); 568 } 569 570 return ""; 571 }