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