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 bool sortCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe {
169     import clang: Cursor;
170     return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace
171         ? lhs.spelling < rhs.spelling
172         : lhs.canonical.sourceRange.start < rhs.canonical.sourceRange.start;
173 }
174 
175 bool sameCursorForChunking(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe {
176     import clang: Cursor;
177     return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace
178         ? lhs.spelling == rhs.spelling
179         : lhs.canonical == rhs.canonical;
180 }
181 
182 
183 from!"clang".Cursor mergeCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
184     in(
185         lhs.kind == rhs.kind
186         || (lhs.kind == from!"clang".Cursor.Kind.StructDecl &&
187             rhs.kind == from!"clang".Cursor.Kind.ClassDecl)
188         || (lhs.kind == from!"clang".Cursor.Kind.ClassDecl &&
189             rhs.kind == from!"clang".Cursor.Kind.StructDecl)
190     )
191     do
192 {
193     import clang: Cursor;
194 
195     return lhs.kind == Cursor.Kind.Namespace
196         ? mergeNodes(lhs, rhs)
197         : mergeLeaves(lhs, rhs);
198 }
199 
200 
201 // Takes two namespaces with the same name and returns a new namespace cursor
202 // with merged declarations from each
203 from!"clang".Cursor mergeNodes(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
204     in(lhs.kind == from!"clang".Cursor.Kind.Namespace &&
205        rhs.kind == from!"clang".Cursor.Kind.Namespace &&
206        lhs.spelling == rhs.spelling)
207     do
208 {
209     import clang: Cursor;
210     import std.algorithm: filter, countUntil;
211     import std.array: front, empty;
212 
213     auto ret = Cursor(Cursor.Kind.Namespace, lhs.spelling);
214     ret.children = canonicalCursors(lhs.children);
215 
216     foreach(child; rhs.children) {
217         const alreadyHaveIndex = ret.children.countUntil!(a => a.kind == child.kind &&
218                                                                a.spelling == child.spelling);
219         // no such cursor yet, add it to the list
220         if(alreadyHaveIndex == -1)
221             ret.children = ret.children ~ child;
222         else {
223             auto merge = child.kind == Cursor.Kind.Namespace ? &mergeNodes : &mergeLeaves;
224 
225             ret.children =
226                 ret.children[0 .. alreadyHaveIndex] ~
227                 ret.children[alreadyHaveIndex + 1 .. $] ~
228                 merge(ret.children[alreadyHaveIndex], child);
229         }
230     }
231 
232     return ret;
233 }
234 
235 
236 // Merges two cursors so that the "best" one is returned. Avoid double definitions
237 // that are allowed in C but not in D
238 from!"clang".Cursor mergeLeaves(from!"clang".Cursor lhs, from!"clang".Cursor rhs)
239     in(lhs.kind != from!"clang".Cursor.Kind.Namespace &&
240        rhs.kind != from!"clang".Cursor.Kind.Namespace)
241     do
242 {
243     import clang: Cursor;
244     import std.algorithm: sort, chunkBy, map, filter;
245     import std.array: array, join;
246     import std.range: chain;
247 
248     // Filter out "ghosts" (useless repeated cursors).
249     // Each element of `cursors` has the same canonical cursor.
250     static Cursor cursorFromCanonicals(Cursor[] cursors) {
251         import clang: Cursor;
252         import std.algorithm: all, filter;
253         import std.array: array, save, front, empty;
254 
255         auto definitions = cursors.save.filter!(a => a.isDefinition);
256         if(!definitions.empty) return definitions.front;
257 
258         auto canonicals = cursors.save.filter!(a => a.isCanonical);
259         if(!canonicals.empty) return canonicals.front;
260 
261         assert(!cursors.empty);
262         return cursors.front;
263     }
264 
265     return cursorFromCanonicals([lhs, rhs]);
266 }
267 
268 
269 
270 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure {
271     import std.path: extension;
272     if(options.parseAsCpp) return true;
273     return headerFileName.extension != ".h";
274 }
275 
276 
277 string getHeaderName(in const(char)[] line, in string[] includePaths)
278     @safe
279 {
280     const name = getHeaderName(line);
281     return name == "" ? name : fullPath(includePaths, name);
282 }
283 
284 
285 string getHeaderName(const(char)[] line)
286     @safe pure
287 {
288     import std.algorithm: startsWith, countUntil;
289     import std.range: dropBack;
290     import std.array: popFront;
291     import std.string: stripLeft;
292 
293     line = line.stripLeft;
294     if(!line.startsWith(`#include `)) return "";
295 
296     const openingQuote = line.countUntil!(a => a == '"' || a == '<');
297     const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1;
298     return line[openingQuote + 1 .. closingQuote].idup;
299 }
300 
301 
302 // transforms a header name, e.g. stdio.h
303 // into a full file path, e.g. /usr/include/stdio.h
304 private string fullPath(in string[] includePaths, in string headerName) @safe {
305 
306     import std.algorithm: map, filter;
307     import std.path: buildPath, absolutePath;
308     import std.file: exists;
309     import std.conv: text;
310     import std.exception: enforce;
311 
312     if(headerName.exists) return headerName;
313 
314     auto filePaths = includePaths
315         .map!(a => buildPath(a, headerName).absolutePath)
316         .filter!exists;
317 
318     enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'"));
319 
320     return filePaths.front;
321 }