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 }