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 }