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 }