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