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++";
70     else
71         parseArgs ~= "-xc";
72 
73     return parse(translUnitFileName,
74                  parseArgs,
75                  TranslationUnitFlags.DetailedPreprocessingRecord);
76 }
77 
78 // returns a range of Cursor
79 private auto canonicalCursors(from!"clang".TranslationUnit translationUnit) @safe {
80 
81     import dpp.translation.translation: translateTopLevelCursor;
82     import clang: Cursor;
83     import std.array: array, join;
84     import std.algorithm: sort, filter, map, chunkBy, all, partition;
85 
86     // In C there can be several declarations and one definition of a type.
87     // In D we can have only ever one of either. There might be multiple
88     // cursors in the translation unit that all refer to the same canonical type.
89     // Unfortunately, the canonical type is orthogonal to which cursor is the actual
90     // definition, so we prefer to find the definition if it exists, and if not, we
91     // take the canonical declaration so as to not repeat ourselves in D.
92     static Cursor[] trueCursors(R)(R cursors) {
93 
94         // we always accept multiple namespaces
95         if(cursors.save.all!(a => a.kind == Cursor.Kind.Namespace))
96             return cursors.array;
97 
98         auto definitions = cursors.save.filter!(a => a.isDefinition);
99         if(!definitions.empty) return [definitions.front];
100 
101         auto canonicals = cursors.save.filter!(a => a.isCanonical);
102         if(!canonicals.empty) return [canonicals.front];
103 
104         assert(!cursors.empty);
105         return [cursors.front];
106     }
107 
108     static bool goodCursor(in Cursor cursor) {
109         return cursor.isCanonical || cursor.isDefinition;
110     }
111 
112     auto cursors = () @trusted {
113         return translationUnit
114         .cursor
115         .children
116         // sort them by canonical cursor
117         .sort!((a, b) => a.canonical.sourceRange.start <
118                          b.canonical.sourceRange.start)
119         // each chunk is a range of cursors representing the same canonical entity
120         .chunkBy!((a, b) => a.canonical == b.canonical)
121         // for each chunk, extract the one cursor we want
122         .map!trueCursors
123         .join  // flatten
124         ;
125     }();
126 
127     cursors.partition!(a => a.kind != Cursor.Kind.MacroDefinition);
128 
129     return cursors;
130 }
131 
132 
133 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure {
134     import std.path: extension;
135     if(options.parseAsCpp) return true;
136     return headerFileName.extension != ".h";
137 }
138 
139 
140 string getHeaderName(in const(char)[] line, in string[] includePaths)
141     @safe
142 {
143     const name = getHeaderName(line);
144     return name == "" ? name : fullPath(includePaths, name);
145 }
146 
147 
148 string getHeaderName(const(char)[] line)
149     @safe pure
150 {
151     import std.algorithm: startsWith, countUntil;
152     import std.range: dropBack;
153     import std.array: popFront;
154     import std..string: stripLeft;
155 
156     line = line.stripLeft;
157     if(!line.startsWith(`#include `)) return "";
158 
159     const openingQuote = line.countUntil!(a => a == '"' || a == '<');
160     const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1;
161     return line[openingQuote + 1 .. closingQuote].idup;
162 }
163 
164 ///
165 @("getHeaderName")
166 @safe pure unittest {
167     import unit_threaded: shouldEqual;
168     getHeaderName(`#include "foo.h"`).shouldEqual(`foo.h`);
169     getHeaderName(`#include "bar.h"`).shouldEqual(`bar.h`);
170     getHeaderName(`#include "foo.h" // comment`).shouldEqual(`foo.h`);
171     getHeaderName(`#include <foo.h>`).shouldEqual(`foo.h`);
172     getHeaderName(`    #include "foo.h"`).shouldEqual(`foo.h`);
173 }
174 
175 
176 
177 // transforms a header name, e.g. stdio.h
178 // into a full file path, e.g. /usr/include/stdio.h
179 private string fullPath(in string[] includePaths, in string headerName) @safe {
180 
181     import std.algorithm: map, filter;
182     import std.path: buildPath, absolutePath;
183     import std.file: exists;
184     import std.conv: text;
185     import std.exception: enforce;
186 
187     if(headerName.exists) return headerName;
188 
189     auto filePaths = includePaths
190         .map!(a => buildPath(a, headerName).absolutePath)
191         .filter!exists;
192 
193     enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'"));
194 
195     return filePaths.front;
196 }