1 /** 2 The context the translation happens in, to avoid global variables 3 */ 4 module dpp.runtime.context; 5 6 alias LineNumber = size_t; 7 8 // A function or global variable 9 struct Linkable { 10 LineNumber lineNumber; 11 string mangling; 12 } 13 14 enum Language { 15 C, 16 Cpp, 17 } 18 19 20 /** 21 Context for the current translation, to avoid global variables 22 */ 23 struct Context { 24 25 import dpp.runtime.options: Options; 26 import clang: Cursor, Type, AccessSpecifier; 27 import std.array: Appender; 28 29 alias SeenCursors = bool[CursorId]; 30 31 private auto lines(this This)() { 32 return _lines.data; 33 } 34 35 /** 36 The lines of output so far. This is needed in order to fix 37 any name collisions between functions or variables with aggregates 38 such as structs, unions and enums. 39 */ 40 private Appender!(string[]) _lines; 41 42 /** 43 Structs can be anonymous in C, and it's even common 44 to typedef them to a name. We come up with new names 45 that we track here so as to be able to properly translate 46 those typedefs. 47 */ 48 private string[Cursor.Hash] _nickNames; 49 50 /** 51 Remembers the seen struct pointers so that if any are undeclared in C, 52 we do so in D at the end. 53 */ 54 private bool[string] _fieldStructSpellings; 55 56 /** 57 Remembers the field spellings in aggregates in case we need to change any 58 of them. 59 */ 60 private LineNumber[][string] _fieldDeclarations; 61 62 /** 63 All the aggregates that have been declared 64 */ 65 private bool[string] _aggregateDeclarations; 66 67 /** 68 Mapping between the original aggregate spelling and the renamed one, 69 if renaming was necessary. 70 */ 71 private string[string] _aggregateSpelling; 72 73 /** 74 Mapping between a child aggregate's name and its parent aggregate's 75 name. 76 */ 77 private string[string] _aggregateParents; 78 79 /** 80 Mapping between a line number and an array of strings representing all 81 the aggregate types' names contained at that index. 82 */ 83 private string[][LineNumber] _aggregateTypeLines; 84 85 /** 86 A linkable is a function or a global variable. We remember all 87 the ones we saw here so that if there's a name clash we can 88 come back and fix the declarations after the fact with 89 pragma(mangle). 90 */ 91 private Linkable[string] _linkableDeclarations; 92 93 /** 94 All the function-like macros that have been declared 95 */ 96 private bool[string] _functionMacroDeclarations; 97 98 /** 99 Remember all the macros already defined 100 */ 101 private bool[string] _macros; 102 103 /** 104 All previously seen cursors 105 */ 106 private SeenCursors _seenCursors; 107 108 AccessSpecifier accessSpecifier = AccessSpecifier.Public; 109 110 /// Command-line options 111 Options options; 112 113 /* 114 Remember all declared types so that C-style casts can be recognised 115 */ 116 private string[] _types; 117 118 /// to generate unique names 119 private int _anonymousIndex; 120 121 private string[] _namespaces; 122 123 Language language; 124 125 this(Options options, in Language language) @safe pure { 126 this.options = options; 127 this.language = language; 128 } 129 130 ref Context indent() @safe pure return { 131 options.indent; 132 return this; 133 } 134 135 auto indentation() @safe @nogc pure const { 136 return options.indentation; 137 } 138 139 void setIndentation(in int indentation) @safe pure { 140 options.indentation = indentation; 141 } 142 143 void log(A...)(auto ref A args) const { 144 import std.functional: forward; 145 options.log(forward!args); 146 } 147 148 bool debugOutput() @safe @nogc pure nothrow const { 149 return options.debugOutput; 150 } 151 152 bool hasSeen(in Cursor cursor) @safe pure nothrow const { 153 return cast(bool)(CursorId(cursor) in _seenCursors); 154 } 155 156 void rememberCursor(in Cursor cursor) @safe pure nothrow { 157 // EnumDecl can have no spelling but end up defining an enum anyway 158 // See "it.compile.projects.double enum typedef" 159 if(cursor.spelling != "" || cursor.kind == Cursor.Kind.EnumDecl) 160 _seenCursors[CursorId(cursor)] = true; 161 } 162 163 string translation() @safe pure nothrow const { 164 import std.array: join; 165 return lines.join("\n"); 166 } 167 168 /** 169 Writes a line of translation. 170 */ 171 void writeln(in string line) @safe pure nothrow { 172 _lines ~= line; 173 } 174 175 /** 176 Writes lines of translation. 177 */ 178 void writeln(in string[] lines) @safe pure nothrow { 179 _lines ~= lines; 180 } 181 182 // remember a function or variable declaration 183 string rememberLinkable(in Cursor cursor) @safe pure nothrow { 184 import dpp.translation.dlang: maybeRename; 185 186 const spelling = maybeRename(cursor, this); 187 // since linkables produce one-line translations, the next 188 // will be the linkable 189 _linkableDeclarations[spelling] = Linkable(lines.length, cursor.mangling); 190 191 return spelling; 192 } 193 194 void fixNames() @safe { 195 declareUnknownStructs; 196 fixLinkables; 197 if (language == Language.C) 198 fixAggregateTypes; 199 fixFields; 200 } 201 202 void fixLinkables() @safe pure { 203 foreach(declarations; [_aggregateDeclarations, _functionMacroDeclarations]) { 204 foreach(name, _; declarations) { 205 // if there's a name clash, fix it 206 auto clashingLinkable = name in _linkableDeclarations; 207 if(clashingLinkable) { 208 resolveClash(lines[clashingLinkable.lineNumber], name, clashingLinkable.mangling); 209 } 210 } 211 } 212 } 213 214 void fixFields() @safe pure { 215 216 import dpp.translation.dlang: pragmaMangle, rename; 217 import std..string: replace; 218 219 foreach(spelling, lineNumbers; _fieldDeclarations) { 220 if(spelling in _aggregateDeclarations || spelling in _aggregateSpelling) { 221 const actual = spelling in _aggregateSpelling 222 ? _aggregateSpelling[spelling] 223 : spelling; 224 const renamed = rename(actual, this); 225 foreach (lineNumber; lineNumbers) { 226 lines[lineNumber] = lines[lineNumber] 227 // Member declaration 228 .replace(" " ~ actual ~ `;`, " " ~ renamed ~ `;`) 229 // Pointer declaration 230 .replace(" *" ~ actual ~ `;`, " *" ~ renamed ~ `;`) 231 // Accessing member in getter (C11 anon records) 232 .replace("." ~ actual ~ ";", "." ~ renamed ~ ";") 233 // Accessing member in setter (C11 anon records) 234 .replace("." ~ actual ~ " =", "." ~ renamed ~ " =") 235 // Getter function name (C11 anon records) 236 .replace("auto " ~ actual ~ "()", "auto " ~ renamed ~ "()") 237 // Setter function name (C11 anon records) 238 .replace("void " ~ actual ~ "(_T_)", "void " ~ renamed ~ "(_T_)"); 239 } 240 } 241 } 242 } 243 244 void fixAggregateTypes() @safe pure { 245 import dpp.translation.type : removeDppDecorators; 246 import std.array : join; 247 import std.algorithm : reverse; 248 import std..string : replace; 249 250 string aggregateTypeName(in string spelling) @safe pure { 251 if (spelling !in _aggregateParents) 252 return spelling; 253 254 string[] elems; 255 elems ~= spelling; 256 string curr = _aggregateParents[spelling]; 257 258 while (curr in _aggregateParents) { 259 elems ~= curr ~ "."; 260 curr = _aggregateParents[curr]; 261 } 262 263 elems ~= curr ~ "."; 264 265 return elems.reverse.join; 266 } 267 268 foreach (elem; _aggregateTypeLines.byKeyValue) { 269 LineNumber lineNumber = elem.key; 270 string[] aggregateTypeNames = elem.value; 271 272 foreach (name; aggregateTypeNames) { 273 const actualName = aggregateTypeName(name); 274 lines[lineNumber] = lines[lineNumber] 275 .replace("__dpp_aggregate__ " ~ name, actualName); 276 } 277 } 278 } 279 280 /** 281 Tells the context to remember a struct type encountered in an aggregate field. 282 Typically this will be a pointer to a structure but it could also be the return 283 type or parameter types of a function pointer field. This is (surprisingly!) 284 perfectly valid C code, even though `Foo` is never declared anywhere: 285 ---------------------- 286 struct Foo* fun(void); 287 ---------------------- 288 See issues #22 and #24 289 */ 290 void rememberFieldStruct(in string typeSpelling) @safe pure { 291 _fieldStructSpellings[typeSpelling] = true; 292 } 293 294 /** 295 In C it's possible for a struct field name to have the same name as a struct 296 because of elaborated names. We remember them here in case we need to fix them. 297 */ 298 void rememberField(scope const string spelling) @safe pure { 299 _fieldDeclarations[spelling] ~= lines.length; 300 } 301 302 /** 303 Remember this aggregate cursor 304 */ 305 void rememberAggregate(in Cursor cursor) @safe pure { 306 const spelling = resolveSpelling(cursor); 307 rememberType(spelling); 308 } 309 310 bool aggregateIsRemembered(in Cursor cursor) @safe pure { 311 import std.algorithm: canFind; 312 const spelling = resolveSpelling(cursor); 313 return _types.canFind(spelling); 314 } 315 316 void rememberAggregateParent(in Cursor child, in Cursor parent) @safe pure { 317 const parentSpelling = spelling(parent.spelling); 318 const childSpelling = resolveSpelling(child); 319 _aggregateParents[childSpelling] = parentSpelling; 320 } 321 322 void rememberAggregateTypeLine(in string typeName) @safe pure { 323 _aggregateTypeLines[lines.length] ~= typeName; 324 } 325 326 private string resolveSpelling(in Cursor cursor) @safe pure { 327 const spelling = spellingOrNickname(cursor); 328 _aggregateDeclarations[spelling] = true; 329 rememberSpelling(cursor.spelling, spelling); 330 return spelling; 331 } 332 333 void rememberSpelling(scope const string original, in string spelling) @safe pure { 334 if (original != "" && original != spelling) 335 _aggregateSpelling[original] = spelling; 336 } 337 338 bool isUnknownStruct(in string name) @safe pure const { 339 import std.algorithm: canFind; 340 return name !in _aggregateDeclarations 341 && (name !in _aggregateSpelling 342 || _aggregateSpelling[name] !in _aggregateDeclarations) 343 && !name.canFind("anonymous at"); 344 } 345 346 /** 347 If unknown structs show up in functions or fields (as a pointer), 348 define them now so the D file can compile 349 See `it.c.compile.delayed`. 350 */ 351 void declareUnknownStructs() @safe { 352 import dpp.translation.type : removeDppDecorators; 353 354 foreach(name, _; _fieldStructSpellings) { 355 name = name.removeDppDecorators; 356 if(isUnknownStruct(name)) { 357 log("Could not find '", name, "' in aggregate declarations, defining it"); 358 const spelling = name in _aggregateSpelling ? _aggregateSpelling[name] 359 : name; 360 writeln("struct " ~ spelling ~ ";"); 361 _aggregateDeclarations[spelling] = true; 362 } 363 } 364 } 365 366 const(typeof(_aggregateDeclarations)) aggregateDeclarations() @safe pure nothrow const { 367 return _aggregateDeclarations; 368 } 369 370 /// return the spelling if it exists, or our made-up nickname for it if not 371 string spellingOrNickname(in Cursor cursor) @safe pure { 372 if (cursor.spelling == "" || cursor.isAnonymous) 373 return nickName(cursor); 374 375 return spelling(cursor.spelling); 376 } 377 378 string spelling(scope const string cursorSpelling) @safe pure { 379 import dpp.translation.dlang: rename, isKeyword; 380 381 if (cursorSpelling in _aggregateSpelling) 382 return _aggregateSpelling[cursorSpelling]; 383 384 return cursorSpelling.isKeyword ? rename(cursorSpelling, this) 385 : cursorSpelling.idup; 386 } 387 388 private string nickName(in Cursor cursor) @safe pure { 389 if(cursor.hash !in _nickNames) { 390 auto nick = newAnonymousTypeName; 391 _nickNames[cursor.hash] = nick; 392 } 393 394 return _nickNames[cursor.hash]; 395 } 396 397 private string newAnonymousTypeName() @safe pure { 398 import std.conv: text; 399 return text("_Anonymous_", _anonymousIndex++); 400 } 401 402 string newAnonymousMemberName() @safe pure { 403 import std..string: replace; 404 return newAnonymousTypeName.replace("_A", "_a"); 405 } 406 407 private void resolveClash(ref string line, in string spelling, in string mangling) @safe pure const { 408 import dpp.translation.dlang: pragmaMangle; 409 line = ` ` ~ pragmaMangle(mangling) ~ replaceSpelling(line, spelling); 410 } 411 412 private string replaceSpelling(in string line, in string spelling) @safe pure const { 413 import dpp.translation.dlang: rename; 414 import std.array: replace; 415 return line 416 .replace(spelling ~ `;`, rename(spelling, this) ~ `;`) 417 .replace(spelling ~ `(`, rename(spelling, this) ~ `(`) 418 ; 419 } 420 421 void rememberType(in string type) @safe pure nothrow { 422 _types ~= type; 423 } 424 425 bool isUserDefinedType(in string spelling) @safe pure const { 426 import std.algorithm: canFind; 427 return _types.canFind(spelling); 428 } 429 430 void rememberMacro(in Cursor cursor) @safe pure { 431 _macros[cursor.spelling.idup] = true; 432 if(cursor.isMacroFunction) 433 _functionMacroDeclarations[cursor.spelling.idup] = true; 434 } 435 436 bool macroAlreadyDefined(in Cursor cursor) @safe pure const { 437 return cast(bool) (cursor.spelling in _macros); 438 } 439 440 void pushNamespace(in string ns) @safe pure nothrow { 441 _namespaces ~= ns; 442 } 443 444 void popNamespace(in string ns) @safe pure nothrow { 445 _namespaces = _namespaces[0 .. $-1]; 446 } 447 448 // returns the current namespace so it can be deleted 449 // from translated names 450 string namespace() @safe pure nothrow const { 451 import std.array: join; 452 return _namespaces.join("::"); 453 } 454 455 /// If this cursor is from one of the ignored namespaces 456 bool isFromIgnoredNs(in Type type) @safe const { 457 import std.algorithm: canFind, any; 458 return options.ignoredNamespaces.any!(a => type.spelling.canFind(a ~ "::")); 459 } 460 461 /// Is the file from an ignored path? Note it uses file globbing 462 bool isFromIgnoredPath(in Cursor cursor) @safe const { 463 import std.path: globMatch; 464 import std.algorithm: any; 465 string sourcePath = cursor.sourceRange.path; 466 return options.ignoredPaths.any!(a => sourcePath.globMatch(a)); 467 } 468 } 469 470 471 // to identify a cursor 472 private struct CursorId { 473 import clang: Cursor, Type; 474 475 string cursorSpelling; 476 Cursor.Kind cursorKind; 477 string typeSpelling; 478 Type.Kind typeKind; 479 480 this(in Cursor cursor) @safe pure nothrow { 481 cursorSpelling = cursor.spelling.idup; 482 cursorKind = cursor.kind; 483 typeSpelling = cursor.type.spelling.idup; 484 typeKind = cursor.type.kind; 485 } 486 }