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, Language; 20 import std.array: join, array; 21 import std.conv: text; 22 import std.algorithm: any, endsWith; 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 // FIXME - stop special casing the move ctor 34 auto moveCtorLines = maybeMoveCtor(cursor, context); 35 if(moveCtorLines) return moveCtorLines; 36 37 string[] lines; 38 39 lines ~= maybeCopyCtor(cursor, context); 40 lines ~= maybeOperator(cursor, context); 41 42 maybeRememberStructs(paramTypes(cursor), context); 43 44 const spelling = functionSpelling(cursor, context); 45 46 lines ~= [ 47 maybePragma(cursor, context) ~ functionDecl(cursor, context, spelling) 48 ]; 49 50 51 context.log(""); 52 53 return lines; 54 } 55 56 private string functionDecl( 57 in from!"clang".Cursor cursor, 58 ref from!"dpp.runtime.context".Context context, 59 in string spelling, 60 in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names 61 ) 62 @safe 63 { 64 import std.conv: text; 65 import std.algorithm: endsWith; 66 import std.array: join; 67 68 context.log("Function return type (raw): ", cursor.type.returnType); 69 const returnType = returnType(cursor, context); 70 context.log("Function return type (translated): ", returnType); 71 72 const params = translateAllParamTypes(cursor, context, names).join(", "); 73 context.log("params: ", params); 74 // const C++ method? 75 const const_ = cursor.isConstCppMethod ? " const" : ""; 76 77 return text(returnType, " ", spelling, "(", params, ") @nogc nothrow", const_, ";"); 78 } 79 80 private string returnType(in from!"clang".Cursor cursor, 81 ref from!"dpp.runtime.context".Context context) 82 @safe 83 { 84 import dpp.translation.type: translate; 85 import clang: Cursor; 86 import std.typecons: Yes; 87 88 const indentation = context.indentation; 89 90 const dType = cursor.kind == Cursor.Kind.Constructor || cursor.kind == Cursor.Kind.Destructor 91 ? "" 92 : translate(cursor.returnType, context, Yes.translatingFunction); 93 94 context.setIndentation(indentation); 95 96 const static_ = cursor.storageClass == Cursor.StorageClass.Static ? "static " : ""; 97 98 return static_ ~ dType; 99 } 100 101 private string[] maybeOperator(in from!"clang".Cursor cursor, 102 ref from!"dpp.runtime.context".Context context) 103 @safe 104 { 105 import std.algorithm: map; 106 import std.array: join; 107 import std.typecons: Yes; 108 import std.range: iota; 109 import std.conv: text; 110 111 if(!isSupportedOperatorInD(cursor)) return []; 112 113 const params = translateAllParamTypes(cursor, context); 114 115 return [ 116 // remove semicolon from the end with [0..$-1] 117 `extern(D) ` ~ functionDecl(cursor, context, operatorSpellingD(cursor, context), Yes.names)[0..$-1], 118 `{`, 119 ` return ` ~ operatorSpellingCpp(cursor, context) ~ `(` ~ params.length.iota.map!(a => text("arg", a)).join(", ") ~ `);`, 120 `}`, 121 ]; 122 } 123 124 private bool isSupportedOperatorInD(in from!"clang".Cursor cursor) @safe nothrow { 125 import clang: Cursor; 126 import std.algorithm: map, canFind; 127 128 if(!isOperator(cursor)) return false; 129 // No D support for free function operator overloads 130 if(cursor.semanticParent.kind == Cursor.Kind.TranslationUnit) return false; 131 132 const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 133 const unsupportedSpellings = [`!`, `,`, `&&`, `||`, `->`, `->*`]; 134 if(unsupportedSpellings.canFind(cppOperator)) return false; 135 136 if(isUnaryOperator(cursor) && cppOperator == "&") return false; 137 138 return true; 139 } 140 141 private bool isOperator(in from!"clang".Cursor cursor) @safe pure nothrow { 142 import std.algorithm: startsWith; 143 return cursor.spelling.startsWith(OPERATOR_PREFIX); 144 } 145 146 147 private string functionSpelling(in from!"clang".Cursor cursor, 148 ref from!"dpp.runtime.context".Context context) 149 @safe 150 { 151 import clang: Cursor; 152 import std.algorithm: startsWith; 153 154 155 if(cursor.kind == Cursor.Kind.Constructor) return "this"; 156 if(cursor.kind == Cursor.Kind.Destructor) return "~this"; 157 158 if(cursor.spelling.startsWith(OPERATOR_PREFIX)) return operatorSpellingCpp(cursor, context); 159 160 // if no special case 161 return context.rememberLinkable(cursor); 162 } 163 164 private string operatorSpellingD(in from!"clang".Cursor cursor, 165 ref from!"dpp.runtime.context".Context context) 166 @safe 167 { 168 import clang: Cursor; 169 import std.range: walkLength; 170 import std.algorithm: canFind; 171 172 const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 173 174 if(cursor.kind == Cursor.Kind.ConversionFunction) { 175 return `opCast(T: ` ~ returnType(cursor, context) ~ `)`; 176 } 177 178 if(cppOperator.length > 1 && 179 cppOperator[$-1] == '=' && 180 (cppOperator.length != 2 || !['=', '!', '<', '>'].canFind(cppOperator[0]))) 181 return `opOpAssign(string op: "` ~ cppOperator[0 .. $-1] ~ `")`; 182 183 assert(isUnaryOperator(cursor) || isBinaryOperator(cursor)); 184 const dFunction = isBinaryOperator(cursor) ? "opBinary" : "opUnary"; 185 186 switch(cppOperator) { 187 default: return dFunction ~ `(string op: "` ~ cppOperator ~ `")`; 188 case "=": return `opAssign`; 189 case "()": return `opCall`; 190 case "[]": return `opIndex`; 191 case "==": return `opEquals`; 192 } 193 } 194 195 private bool isUnaryOperator(in from!"clang".Cursor cursor) @safe nothrow { 196 import std.range: walkLength; 197 return isOperator(cursor) && paramTypes(cursor).walkLength == 0; 198 } 199 200 private bool isBinaryOperator(in from!"clang".Cursor cursor) @safe nothrow { 201 import std.range: walkLength; 202 return isOperator(cursor) && paramTypes(cursor).walkLength == 1; 203 } 204 205 206 private string operatorSpellingCpp(in from!"clang".Cursor cursor, 207 ref from!"dpp.runtime.context".Context context) 208 @safe 209 { 210 import dpp.translation.type: translate; 211 import clang: Cursor; 212 import std..string: replace; 213 214 const operator = cursor.spelling[OPERATOR_PREFIX.length .. $]; 215 216 if(cursor.kind == Cursor.Kind.ConversionFunction) { 217 return "opCppCast_" ~ translate(cursor.returnType, context).replace(".", "_"); 218 } 219 220 switch(operator) { 221 default: throw new Exception("Unknown C++ spelling for operator '" ~ operator ~ "'"); 222 case "+": return `opCppPlus`; 223 case "-": return `opCppMinus`; 224 case "++": return `opCppIncrement`; 225 case "--": return `opCppDecrement`; 226 case "*": return `opCppMul`; 227 case "/": return `opCppDiv`; 228 case "&": return `opCppAmpersand`; 229 case "~": return `opCppTilde`; 230 case "%": return `opCppMod`; 231 case "^": return `opCppCaret`; 232 case "|": return `opCppPipe`; 233 case "=": return `opCppAssign`; 234 case ">>": return `opCppRShift`; 235 case "<<": return `opCppLShift`; 236 case "->": return `opCppArrow`; 237 case "!": return `opCppBang`; 238 case "&&": return `opCppAnd`; 239 case "||": return `opCppOr`; 240 case ",": return `opCppComma`; 241 case "->*": return `opCppArrowStar`; 242 case "+=": return `opCppPlusAssign`; 243 case "-=": return `opCppMinusAssign`; 244 case "*=": return `opCppMulAssign`; 245 case "/=": return `opCppDivAssign`; 246 case "%=": return `opCppModAssign`; 247 case "^=": return `opCppCaretAssign`; 248 case "&=": return `opCppAmpersandAssign`; 249 case "|=": return `opCppPipeAssign`; 250 case ">>=": return `opCppRShiftAssign`; 251 case "<<=": return `opCppLShiftAssign`; 252 case "()": return `opCppCall`; 253 case "[]": return `opCppIndex`; 254 case "==": return `opCppEquals`; 255 case "!=": return `opCppNotEquals`; 256 case "<=": return `opCppLessEquals`; 257 case ">=": return `opCppMoreEquals`; 258 case "<": return `opCppLess`; 259 case ">": return `opCppMore`; 260 case " new": return `opCppNew`; 261 case " new[]": return `opCppNewArray`; 262 case " delete": return `opCppDelete`; 263 case " delete[]": return `opCppDeleteArray`; 264 } 265 266 assert(0); 267 } 268 269 270 // Add a non-const ref that forwards to the const ref copy ctor 271 // so that lvalues don't match the by-value ctor 272 private string[] maybeCopyCtor(in from!"clang".Cursor cursor, 273 ref from!"dpp.runtime.context".Context context) 274 @safe 275 { 276 277 import dpp.translation.dlang: maybeRename, maybePragma; 278 import dpp.translation.type: translate; 279 import clang: Cursor, Type; 280 281 if(!cursor.isCopyConstructor) return []; 282 283 const paramType = () @trusted { return paramTypes(cursor).front; }(); 284 const pointee = translate(paramType.pointee, context); 285 const dType = pointee["const(".length .. $ - 1]; // remove the constness 286 287 return [ 288 `this(ref ` ~ dType ~ ` other)`, 289 `{`, 290 ` this(*cast(const ` ~ dType ~ `*) &other);`, 291 `}`, 292 ]; 293 } 294 295 private string[] maybeMoveCtor(in from!"clang".Cursor cursor, 296 ref from!"dpp.runtime.context".Context context) 297 @safe 298 { 299 300 import dpp.translation.dlang: maybeRename, maybePragma; 301 import dpp.translation.type: translate; 302 import clang: Cursor, Type; 303 304 if(!cursor.isMoveConstructor) return []; 305 306 const paramType = () @trusted { return paramTypes(cursor).front; }(); 307 const pointee = translate(paramType.pointee, context); 308 309 return [ 310 maybePragma(cursor, context) ~ " this(" ~ pointee ~ "*);", 311 "this(" ~ translate(paramType, context) ~ " wrapper) {", 312 " this(wrapper.ptr);", 313 " *wrapper.ptr = typeof(*wrapper.ptr).init;", 314 "}", 315 "this(" ~ pointee ~ " other)", 316 "{", 317 " this(&other);", 318 "}", 319 ]; 320 } 321 322 // includes variadic params 323 private auto translateAllParamTypes( 324 in from!"clang".Cursor cursor, 325 ref from!"dpp.runtime.context".Context context, 326 in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names, 327 ) 328 @safe 329 { 330 import std.algorithm: endsWith, map; 331 import std.array: array; 332 import std.range: enumerate; 333 import std.conv: text; 334 335 // Here we used to check that if there were no parameters and the language is C, 336 // then the correct translation in D would be (...); 337 // However, that's not allowed in D. It just so happens that C production code 338 // exists that doesn't bother with (void), so instead of producing something that 339 // doesn't compile, we compromise and assume the user meant (void) 340 341 const paramTypes = translateParamTypes(cursor, context).array; 342 const isVariadic = cursor.type.spelling.endsWith("...)"); 343 const variadicParams = isVariadic ? ["..."] : []; 344 345 return enumerate(paramTypes ~ variadicParams) 346 .map!(a => names ? a[1] ~ text(" arg", a[0]) : a[1]) 347 .array; 348 } 349 350 auto translateParamTypes(in from!"clang".Cursor cursor, 351 ref from!"dpp.runtime.context".Context context) 352 @safe 353 { 354 import dpp.translation.type: translate; 355 import clang: Type, Language; 356 import std.algorithm: map; 357 import std.range: tee; 358 import std.typecons: Yes; 359 360 // See #43 361 const(Type) deunexpose(in Type type) { 362 return type.kind == Type.Kind.Unexposed && cursor.language != Language.CPlusPlus 363 ? type.canonical 364 : type; 365 } 366 367 return paramTypes(cursor) 368 .tee!((a){ context.log(" Function Child: ", a, " canonical ", a.canonical); }) 369 .map!(a => translate(deunexpose(a), context, Yes.translatingFunction)) 370 ; 371 } 372 373 private auto paramTypes(in from!"clang".Cursor cursor) 374 @safe 375 { 376 import clang: Cursor; 377 import std.algorithm: map, filter; 378 379 return cursor 380 .children 381 .filter!(a => a.kind == Cursor.Kind.ParmDecl) 382 .map!(a => a.type) 383 ; 384 }