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