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_ = context.language == Language.Cpp ? "extern(C++)" : "extern(C)";
29     context.writeln([extern_, "{"]);
30 
31     auto translationUnit = parseTU(translUnitFileName, context, includePaths);
32     auto cursors = canonicalCursors(translationUnit);
33 
34     foreach(cursor; cursors) {
35 
36         if(context.hasSeen(cursor)) continue;
37         context.rememberCursor(cursor);
38 
39         const indentation = context.indentation;
40         const lines = translateTopLevelCursor(cursor, context, file, line);
41         if(lines.length) context.writeln(lines);
42         context.setIndentation(indentation);
43     }
44 
45     context.writeln(["}", ""]);
46     context.writeln("");
47 }
48 
49 
50 private from!"clang".TranslationUnit parseTU
51     (
52         in string translUnitFileName,
53         ref from!"dpp.runtime.context".Context context,
54         in string[] includePaths,
55     )
56     @safe
57 {
58     import dpp.runtime.context: Language;
59     import clang: parse, TranslationUnitFlags;
60     import std.array: array;
61     import std.algorithm: map;
62 
63     auto parseArgs =
64         includePaths.map!(a => "-I" ~ a).array ~
65         context.options.defines.map!(a => "-D" ~ a).array
66         ;
67 
68     if(context.options.parseAsCpp || context.language == Language.Cpp)
69         parseArgs ~= ["-xc++", "-std=c++14"];
70     else
71         parseArgs ~= "-xc";
72 
73     return parse(translUnitFileName,
74                  parseArgs,
75                  TranslationUnitFlags.DetailedPreprocessingRecord);
76 }
77 
78 /**
79    In C there can be several declarations and one definition of a type.
80    In D we can have only ever one of either. There might be multiple
81    cursors in the translation unit that all refer to the same canonical type.
82    Unfortunately, the canonical type is orthogonal to which cursor is the actual
83    definition, so we prefer to find the definition if it exists, and if not, we
84    take the canonical declaration so as to not repeat ourselves in D.
85 
86    Returns: range of clang.Cursor
87 */
88 private auto canonicalCursors(from!"clang".TranslationUnit translationUnit) @safe {
89 
90     import clang: Cursor;
91     import std.algorithm: filter, partition;
92 
93     // Filter out "ghosts" (useless repeated cursors).
94     // Each element of `cursors` has the same canonical cursor.
95     static Cursor[] trueCursorsFromSameCanonical(R)(R cursors) {
96         import clang: Cursor;
97         import std.algorithm: all, filter;
98         import std.array: array;
99 
100         // we always accept multiple namespaces
101         if(cursors.save.all!(a => a.kind == Cursor.Kind.Namespace))
102             return cursors.array;
103 
104         auto definitions = cursors.save.filter!(a => a.isDefinition);
105         if(!definitions.empty) return [definitions.front];
106 
107         auto canonicals = cursors.save.filter!(a => a.isCanonical);
108         if(!canonicals.empty) return [canonicals.front];
109 
110         assert(!cursors.empty);
111         return [cursors.front];
112     }
113 
114     // Given an arbitrary range of cursors, returns a new range filtering out
115     // the "ghosts" (useless repeated cursors).
116     static auto trueCursors(R)(R cursors) @trusted {
117         import std.algorithm: sort, chunkBy, map;
118         import std.array: array, join;
119 
120         return cursors
121             .array  // needed by sort
122             .sort!((a, b) => a.canonical.sourceRange.start <
123                              b.canonical.sourceRange.start)
124             // each chunk is a range of cursors representing the same canonical entity
125             .chunkBy!((a, b) => a.canonical == b.canonical)
126             // for each chunk, extract the one cursor we want
127             .map!trueCursorsFromSameCanonical
128             .join  // flatten (range of chunks of cursors -> range of cursors)
129             ;
130     }
131 
132     auto topLevelCursors = translationUnit.cursor.children;
133     auto nsCursors = topLevelCursors.filter!(c => c.kind == Cursor.Kind.Namespace);
134     auto nonNsCursors = topLevelCursors.filter!(c => c.kind != Cursor.Kind.Namespace);
135 
136     auto trueNsCursors = () @trusted {
137         import clang: Cursor;
138         import std.algorithm: map, chunkBy;
139         import std.typecons: tuple;
140         import std.array: array, join;
141 
142         return nsCursors
143         // each chunk is a range of NS cursors with the same name
144         .chunkBy!((a, b) => a.spelling == b.spelling)
145         // a range of ("namespace", childrenCursors) tuples
146         .map!(nsChunk => tuple(nsChunk.front.spelling, nsChunk.map!(ns => ns.children).join))
147         // convert the children to true cursors (filter out ghosts)
148         .map!(t => tuple(t[0],  // namespace spelling
149                          trueCursors(t[1] /*child cursors*/)))
150         // map each tuple to a new Namespace cursor with the "correct" children
151         .map!((t) { auto c = Cursor(Cursor.Kind.Namespace, t[0]); c.children = t[1]; return c; })
152         .array
153         ;
154     }();
155 
156     auto cursors = trueCursors(nonNsCursors) ~ trueNsCursors;
157 
158     // put the macros at the end
159     cursors.partition!(a => a.kind != Cursor.Kind.MacroDefinition);
160 
161     return cursors;
162 }
163 
164 
165 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure {
166     import std.path: extension;
167     if(options.parseAsCpp) return true;
168     return headerFileName.extension != ".h";
169 }
170 
171 
172 string getHeaderName(in const(char)[] line, in string[] includePaths)
173     @safe
174 {
175     const name = getHeaderName(line);
176     return name == "" ? name : fullPath(includePaths, name);
177 }
178 
179 
180 string getHeaderName(const(char)[] line)
181     @safe pure
182 {
183     import std.algorithm: startsWith, countUntil;
184     import std.range: dropBack;
185     import std.array: popFront;
186     import std..string: stripLeft;
187 
188     line = line.stripLeft;
189     if(!line.startsWith(`#include `)) return "";
190 
191     const openingQuote = line.countUntil!(a => a == '"' || a == '<');
192     const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1;
193     return line[openingQuote + 1 .. closingQuote].idup;
194 }
195 
196 ///
197 @("getHeaderName")
198 @safe pure unittest {
199     import unit_threaded: shouldEqual;
200     getHeaderName(`#include "foo.h"`).shouldEqual(`foo.h`);
201     getHeaderName(`#include "bar.h"`).shouldEqual(`bar.h`);
202     getHeaderName(`#include "foo.h" // comment`).shouldEqual(`foo.h`);
203     getHeaderName(`#include <foo.h>`).shouldEqual(`foo.h`);
204     getHeaderName(`    #include "foo.h"`).shouldEqual(`foo.h`);
205 }
206 
207 
208 
209 // transforms a header name, e.g. stdio.h
210 // into a full file path, e.g. /usr/include/stdio.h
211 private string fullPath(in string[] includePaths, in string headerName) @safe {
212 
213     import std.algorithm: map, filter;
214     import std.path: buildPath, absolutePath;
215     import std.file: exists;
216     import std.conv: text;
217     import std.exception: enforce;
218 
219     if(headerName.exists) return headerName;
220 
221     auto filePaths = includePaths
222         .map!(a => buildPath(a, headerName).absolutePath)
223         .filter!exists;
224 
225     enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'"));
226 
227     return filePaths.front;
228 }