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         import dpp.clang: hasAnonymousSpelling;
341         return name !in _aggregateDeclarations
342             && (name !in _aggregateSpelling
343             || _aggregateSpelling[name] !in _aggregateDeclarations)
344             && !hasAnonymousSpelling(name);
345     }
346 
347     /**
348        If unknown structs show up in functions or fields (as a pointer),
349         define them now so the D file can compile
350         See `it.c.compile.delayed`.
351     */
352     void declareUnknownStructs() @safe {
353         import dpp.translation.type : removeDppDecorators;
354 
355         foreach(name, _; _fieldStructSpellings) {
356             name = name.removeDppDecorators;
357             if(isUnknownStruct(name)) {
358                 log("Could not find '", name, "' in aggregate declarations, defining it");
359                 const spelling = name in _aggregateSpelling ? _aggregateSpelling[name]
360                                                             : name;
361                 writeln("struct " ~ spelling ~ ";");
362                 _aggregateDeclarations[spelling] = true;
363             }
364         }
365     }
366 
367     const(typeof(_aggregateDeclarations)) aggregateDeclarations() @safe pure nothrow const {
368         return _aggregateDeclarations;
369     }
370 
371     /// return the spelling if it exists, or our made-up nickname for it if not
372     string spellingOrNickname(in Cursor cursor) @safe pure {
373         if (cursor.spelling == "" || cursor.isAnonymous)
374             return nickName(cursor);
375 
376         return spelling(cursor.spelling);
377     }
378 
379     string spelling(const string cursorSpelling) @safe pure {
380         import dpp.translation.dlang: rename, isKeyword;
381 
382         if (cursorSpelling in _aggregateSpelling)
383             return _aggregateSpelling[cursorSpelling];
384 
385         return cursorSpelling.isKeyword ? rename(cursorSpelling, this)
386                                         : cursorSpelling;
387     }
388 
389     private string nickName(in Cursor cursor) @safe pure {
390         if(cursor.hash !in _nickNames) {
391             auto nick = newAnonymousTypeName;
392             _nickNames[cursor.hash] = nick;
393         }
394 
395         return _nickNames[cursor.hash];
396     }
397 
398     private string newAnonymousTypeName() @safe pure {
399         import std.conv: text;
400         return text("_Anonymous_", _anonymousIndex++);
401     }
402 
403     string newAnonymousMemberName() @safe pure {
404         import std.string: replace;
405         return newAnonymousTypeName.replace("_A", "_a");
406     }
407 
408     private void resolveClash(ref string line, in string spelling, in string mangling) @safe pure const {
409         import dpp.translation.dlang: pragmaMangle;
410         line = `    ` ~ pragmaMangle(mangling) ~ replaceSpelling(line, spelling);
411     }
412 
413     private string replaceSpelling(in string line, in string spelling) @safe pure const {
414         import dpp.translation.dlang: rename;
415         import std.array: replace;
416         return line
417             .replace(spelling ~ `;`, rename(spelling, this) ~ `;`)
418             .replace(spelling ~ `(`, rename(spelling, this) ~ `(`)
419             ;
420     }
421 
422     void rememberType(in string type) @safe pure nothrow {
423         _types ~= type;
424     }
425 
426     bool isUserDefinedType(in string spelling) @safe pure const {
427         import std.algorithm: canFind;
428         return _types.canFind(spelling);
429     }
430 
431     void rememberMacro(in Cursor cursor) @safe pure {
432         _macros[cursor.spelling.idup] = true;
433         if(cursor.isMacroFunction)
434             _functionMacroDeclarations[cursor.spelling.idup] = true;
435     }
436 
437     bool macroAlreadyDefined(in Cursor cursor) @safe pure const {
438         return cast(bool) (cursor.spelling in _macros);
439     }
440 
441     void pushNamespace(in string ns) @safe pure nothrow {
442         _namespaces ~= ns;
443     }
444 
445     void popNamespace(in string ns) @safe pure nothrow {
446         _namespaces = _namespaces[0 .. $-1];
447     }
448 
449     // returns the current namespace so it can be deleted
450     // from translated names
451     string namespace() @safe pure nothrow const {
452         import std.array: join;
453         return _namespaces.join("::");
454     }
455 
456     /// If this cursor is from one of the ignored namespaces
457     bool isFromIgnoredNs(in Type type) @safe const {
458         import std.algorithm: canFind, any;
459         return options.ignoredNamespaces.any!(a => type.spelling.canFind(a ~ "::"));
460     }
461 
462     /// Is the file from an ignored path? Note it uses file globbing
463     bool isFromIgnoredPath(in Cursor cursor) @safe const {
464         import std.path: globMatch;
465         import std.algorithm: any;
466         string sourcePath = cursor.sourceRange.path;
467         return options.ignoredPaths.any!(a => sourcePath.globMatch(a));
468     }
469 }
470 
471 
472 // to identify a cursor
473 private struct CursorId {
474     import clang: Cursor, Type;
475 
476     string cursorSpelling;
477     Cursor.Kind cursorKind;
478     string typeSpelling;
479     Type.Kind typeKind;
480 
481     this(in Cursor cursor) @safe pure nothrow {
482         cursorSpelling = cursor.spelling.idup;
483         cursorKind = cursor.kind;
484         typeSpelling = cursor.type.spelling.idup;
485         typeKind = cursor.type.kind;
486     }
487 }