1 /**
2    Deals with expanding #include directives inline.
3  */
4 module dpp.expansion;
5 
6 
7 import dpp.from;
8 
9 
10 /**
11    Params:
12        translUnitFileName = The file name with all #include directives to parse
13        context = The translation context
14        language = Whether it's a C or C++ file
15        includePaths = The list of files to pass as -I options to clang
16  */
17 void expand(in string translUnitFileName,
18             ref from!"dpp.runtime.context".Context context,
19             in string[] includePaths,
20             in string file = __FILE__,
21             in size_t line = __LINE__)
22     @safe
23 {
24     import dpp.translation.translation: translateTopLevelCursor;
25     import dpp.runtime.context: Language;
26     import clang: Cursor;
27 
28     const extern_ = () {
29         final switch(context.language) {
30             case Language.Cpp:
31                 return "extern(C++)";
32             case Language.C:
33                 return "extern(C)";
34         }
35     }();
36 
37     context.writeln([extern_, "{"]);
38 
39     auto translationUnit = parseTU(translUnitFileName, context, includePaths);
40     auto cursors = canonicalCursors(translationUnit);
41 
42     foreach(cursor; cursors) {
43 
44         if(context.hasSeen(cursor)) continue;
45         context.rememberCursor(cursor);
46 
47         const indentation = context.indentation;
48         const lines = translateTopLevelCursor(cursor, context, file, line);
49         if(lines.length) context.writeln(lines);
50         context.setIndentation(indentation);
51     }
52 
53     context.writeln(["}", ""]);
54     context.writeln("");
55 }
56 
57 
58 private from!"clang".TranslationUnit parseTU
59     (
60         in string translUnitFileName,
61         ref from!"dpp.runtime.context".Context context,
62         in string[] includePaths,
63     )
64     @safe
65 {
66     import dpp.runtime.context: Language;
67     import clang: parse, TranslationUnitFlags;
68     import std.array: array;
69     import std.algorithm: map;
70     import std.range: chain;
71 
72     auto parseArgs = chain(
73         includePaths.map!(a => "-I" ~ a),
74         context.options.defines.map!(a => "-D" ~ a),
75         context.options.clangOptions
76         )
77         .array;
78 
79     if(context.options.parseAsCpp || context.language == Language.Cpp) {
80         const std = "-std=" ~ context.options.cppStandard;
81         parseArgs ~= ["-xc++", std];
82     } else
83         parseArgs ~= "-xc";
84 
85     return parse(translUnitFileName,
86                  parseArgs,
87                  TranslationUnitFlags.DetailedPreprocessingRecord);
88 }
89 
90 
91 /**
92    In C there can be several declarations and one definition of a type.
93    In D we can have only ever one of either. There might be multiple
94    cursors in the translation unit that all refer to the same canonical type.
95    Unfortunately, the canonical type is orthogonal to which cursor is the actual
96    definition, so we prefer to find the definition if it exists, and if not, we
97    take the canonical declaration so as to not repeat ourselves in D.
98 */
99 from!"clang".Cursor[] canonicalCursors(from!"clang".TranslationUnit translationUnit) @safe {
100     // translationUnit isn't const because the cursors need to be sorted
101 
102     import clang: Cursor;
103     import std.algorithm: filter, partition;
104     import std.range: chain;
105     import std.array: array;
106 
107     auto topLevelCursors = translationUnit.cursor.children;
108     return canonicalCursors(topLevelCursors);
109 }
110 
111 
112 /**
113    In C there can be several declarations and one definition of a type.
114    In D we can have only ever one of either. There might be multiple
115    cursors in the translation unit that all refer to the same canonical type.
116    Unfortunately, the canonical type is orthogonal to which cursor is the actual
117    definition, so we prefer to find the definition if it exists, and if not, we
118    take the canonical declaration so as to not repeat ourselves in D.
119 */
120 from!"clang".Cursor[] canonicalCursors(R)(R cursors) @safe {
121     import clang: Cursor;
122     import std.algorithm: filter, partition;
123     import std.range: chain;
124     import std.array: array;
125 
126     auto leafCursors = cursors.filter!(c => c.kind != Cursor.Kind.Namespace);
127     auto nsCursors = cursors.filter!(c => c.kind == Cursor.Kind.Namespace);
128 
129     auto ret =
130         chain(trueCursors(leafCursors), trueCursors(nsCursors))
131         .array;
132 
133     // put the macros at the end
134     ret.partition!(a => a.kind != Cursor.Kind.MacroDefinition);
135 
136     return ret;
137 }
138 
139 
140 // Given an arbitrary range of cursors, returns a new range filtering out
141 // the "ghosts" (useless repeated cursors).
142 // Only works when there are no namespaces
143 from!"clang".Cursor[] trueCursors(R)(R cursors) @trusted /* who knows */ {
144     import clang: Cursor;
145     import std.algorithm: chunkBy, fold, map, sort;
146     import std.array: array;
147 
148     auto ret =
149         cursors
150         // each chunk is a range of cursors with the same name
151         .array  // needed by sort
152         .sort!sortCursors
153         // each chunk is a range of cursors representing the same canonical entity
154         .chunkBy!sameCursorForChunking
155         .map!(chunk => chunk.fold!mergeCursors)
156         .array
157         ;
158 
159     // if there's only one namespace, the fold above does nothing,
160     // so we make the children cursors canonical here
161     if(ret.length == 1 && ret[0].kind == Cursor.Kind.Namespace) {
162         ret[0].children = canonicalCursors(ret[0].children);
163     }
164 
165     return ret;
166 }
167 
168 
169 bool sortCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe {
170     import clang: Cursor;
171 
172     // Tt's possible that two cursors have canonical cursors at the same location
173     // but that aren't of the same kind. An example is if a struct declaration
174     // shows up in a function declaration via a pointer to an yet undeclared/
175     // undefined struct, e.g.
176     // -----------------
177     // struct Foo* getFoo(void);
178     // -----------------
179     // Above, we get a StructDecl for `Foo` and a FuncDecl for `getFoo`,
180     // both at the same source range starting location.
181 
182     bool sortLocation() {
183         const lRange = lhs.canonical.sourceRange;
184         const rRange = rhs.canonical.sourceRange;
185         return lRange.start == rRange.start
186             ? lRange.end < rRange.end
187             : lRange.start < rRange.start;
188     }
189 
190     return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace
191         ? lhs.spelling < rhs.spelling
192         : sortLocation;
193 }
194 
195 
196 bool sameCursorForChunking(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe {
197     import clang: Cursor;
198     return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace
199         ? lhs.spelling == rhs.spelling
200         : lhs.canonical == rhs.canonical;
201 }
202 
203 
204 from!"clang".Cursor mergeCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
205     in(
206         lhs.kind == rhs.kind
207         || (lhs.kind == from!"clang".Cursor.Kind.StructDecl &&
208             rhs.kind == from!"clang".Cursor.Kind.ClassDecl)
209         || (lhs.kind == from!"clang".Cursor.Kind.ClassDecl &&
210             rhs.kind == from!"clang".Cursor.Kind.StructDecl)
211     )
212     do
213 {
214     import clang: Cursor;
215 
216     return lhs.kind == Cursor.Kind.Namespace
217         ? mergeNodes(lhs, rhs)
218         : mergeLeaves(lhs, rhs);
219 }
220 
221 
222 // Takes two namespaces with the same name and returns a new namespace cursor
223 // with merged declarations from each
224 from!"clang".Cursor mergeNodes(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
225     in(lhs.kind == from!"clang".Cursor.Kind.Namespace &&
226        rhs.kind == from!"clang".Cursor.Kind.Namespace &&
227        lhs.spelling == rhs.spelling)
228     do
229 {
230     import clang: Cursor;
231     import std.algorithm: filter, countUntil;
232     import std.array: front, empty;
233 
234     auto ret = Cursor(Cursor.Kind.Namespace, lhs.spelling);
235     ret.children = canonicalCursors(lhs.children);
236 
237     foreach(child; rhs.children) {
238         const alreadyHaveIndex = ret.children.countUntil!(a => a.kind == child.kind &&
239                                                                a.spelling == child.spelling);
240         // no such cursor yet, add it to the list
241         if(alreadyHaveIndex == -1)
242             ret.children = ret.children ~ child;
243         else {
244             auto merge = child.kind == Cursor.Kind.Namespace ? &mergeNodes : &mergeLeaves;
245 
246             ret.children =
247                 ret.children[0 .. alreadyHaveIndex] ~
248                 ret.children[alreadyHaveIndex + 1 .. $] ~
249                 merge(ret.children[alreadyHaveIndex], child);
250         }
251     }
252 
253     return ret;
254 }
255 
256 
257 // Merges two cursors so that the "best" one is returned. Avoid double definitions
258 // that are allowed in C but not in D
259 from!"clang".Cursor mergeLeaves(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
260     in(lhs.kind != from!"clang".Cursor.Kind.Namespace &&
261        rhs.kind != from!"clang".Cursor.Kind.Namespace)
262     do
263 {
264     import clang: Cursor;
265     import std.algorithm: sort, chunkBy, map, filter;
266     import std.array: array, join;
267     import std.range: chain;
268 
269     // Filter out "ghosts" (useless repeated cursors).
270     // Each element of `cursors` has the same canonical cursor.
271     static Cursor cursorFromCanonicals(Cursor[] cursors) {
272         import clang: Cursor;
273         import std.algorithm: all, filter;
274         import std.array: array, save, front, empty;
275 
276         auto definitions = cursors.save.filter!(a => a.isDefinition);
277         if(!definitions.empty) return definitions.front;
278 
279         auto canonicals = cursors.save.filter!(a => a.isCanonical);
280         if(!canonicals.empty) return canonicals.front;
281 
282         assert(!cursors.empty);
283         return cursors.front;
284     }
285 
286     return cursorFromCanonicals([lhs, rhs]);
287 }
288 
289 
290 
291 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure {
292     import std.path: extension;
293     if(options.parseAsCpp) return true;
294     return headerFileName.extension != ".h";
295 }
296 
297 
298 string getHeaderName(in const(char)[] line, in string[] includePaths)
299     @safe
300 {
301     const name = getHeaderName(line);
302     return name == "" ? name : fullPath(includePaths, name);
303 }
304 
305 
306 string getHeaderName(const(char)[] line)
307     @safe pure
308 {
309     import std.algorithm: startsWith, countUntil;
310     import std.range: dropBack;
311     import std.array: popFront;
312     import std..string: stripLeft;
313 
314     line = line.stripLeft;
315     if(!line.startsWith(`#include `)) return "";
316 
317     const openingQuote = line.countUntil!(a => a == '"' || a == '<');
318     const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1;
319     return line[openingQuote + 1 .. closingQuote].idup;
320 }
321 
322 
323 // transforms a header name, e.g. stdio.h
324 // into a full file path, e.g. /usr/include/stdio.h
325 private string fullPath(in string[] includePaths, in string headerName) @safe {
326 
327     import std.algorithm: map, filter;
328     import std.path: buildPath, absolutePath;
329     import std.file: exists;
330     import std.conv: text;
331     import std.exception: enforce;
332 
333     if(headerName.exists) return headerName;
334 
335     auto filePaths = includePaths
336         .map!(a => buildPath(a, headerName).absolutePath)
337         .filter!exists;
338 
339     enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'"));
340 
341     return filePaths.front;
342 }