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 }