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 169 bool sortCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe { 170 import clang: Cursor; 171 172 // Tt's possible that two cursors have canonical cursors at the same location 173 // but that aren't of the same kind. An example is if a struct declaration 174 // shows up in a function declaration via a pointer to an yet undeclared/ 175 // undefined struct, e.g. 176 // ----------------- 177 // struct Foo* getFoo(void); 178 // ----------------- 179 // Above, we get a StructDecl for `Foo` and a FuncDecl for `getFoo`, 180 // both at the same source range starting location. 181 182 bool sortLocation() { 183 const lRange = lhs.canonical.sourceRange; 184 const rRange = rhs.canonical.sourceRange; 185 return lRange.start == rRange.start 186 ? lRange.end < rRange.end 187 : lRange.start < rRange.start; 188 } 189 190 return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace 191 ? lhs.spelling < rhs.spelling 192 : sortLocation; 193 } 194 195 196 bool sameCursorForChunking(from!"clang".Cursor lhs, from!"clang".Cursor rhs) @safe { 197 import clang: Cursor; 198 return lhs.kind == Cursor.Kind.Namespace && rhs.kind == Cursor.Kind.Namespace 199 ? lhs.spelling == rhs.spelling 200 : lhs.canonical == rhs.canonical; 201 } 202 203 204 from!"clang".Cursor mergeCursors(from!"clang".Cursor lhs, from!"clang".Cursor rhs) 205 in( 206 lhs.kind == rhs.kind 207 || (lhs.kind == from!"clang".Cursor.Kind.StructDecl && 208 rhs.kind == from!"clang".Cursor.Kind.ClassDecl) 209 || (lhs.kind == from!"clang".Cursor.Kind.ClassDecl && 210 rhs.kind == from!"clang".Cursor.Kind.StructDecl) 211 ) 212 do 213 { 214 import clang: Cursor; 215 216 return lhs.kind == Cursor.Kind.Namespace 217 ? mergeNodes(lhs, rhs) 218 : mergeLeaves(lhs, rhs); 219 } 220 221 222 // Takes two namespaces with the same name and returns a new namespace cursor 223 // with merged declarations from each 224 from!"clang".Cursor mergeNodes(from!"clang".Cursor lhs, from!"clang".Cursor rhs) 225 in(lhs.kind == from!"clang".Cursor.Kind.Namespace && 226 rhs.kind == from!"clang".Cursor.Kind.Namespace && 227 lhs.spelling == rhs.spelling) 228 do 229 { 230 import clang: Cursor; 231 import std.algorithm: filter, countUntil; 232 import std.array: front, empty; 233 234 auto ret = Cursor(Cursor.Kind.Namespace, lhs.spelling); 235 ret.children = canonicalCursors(lhs.children); 236 237 foreach(child; rhs.children) { 238 const alreadyHaveIndex = ret.children.countUntil!(a => a.kind == child.kind && 239 a.spelling == child.spelling); 240 // no such cursor yet, add it to the list 241 if(alreadyHaveIndex == -1) 242 ret.children = ret.children ~ child; 243 else { 244 auto merge = child.kind == Cursor.Kind.Namespace ? &mergeNodes : &mergeLeaves; 245 246 ret.children = 247 ret.children[0 .. alreadyHaveIndex] ~ 248 ret.children[alreadyHaveIndex + 1 .. $] ~ 249 merge(ret.children[alreadyHaveIndex], child); 250 } 251 } 252 253 return ret; 254 } 255 256 257 // Merges two cursors so that the "best" one is returned. Avoid double definitions 258 // that are allowed in C but not in D 259 from!"clang".Cursor mergeLeaves(from!"clang".Cursor lhs, from!"clang".Cursor rhs) 260 in(lhs.kind != from!"clang".Cursor.Kind.Namespace && 261 rhs.kind != from!"clang".Cursor.Kind.Namespace) 262 do 263 { 264 import clang: Cursor; 265 import std.algorithm: sort, chunkBy, map, filter; 266 import std.array: array, join; 267 import std.range: chain; 268 269 // Filter out "ghosts" (useless repeated cursors). 270 // Each element of `cursors` has the same canonical cursor. 271 static Cursor cursorFromCanonicals(Cursor[] cursors) { 272 import clang: Cursor; 273 import std.algorithm: all, filter; 274 import std.array: array, save, front, empty; 275 276 auto definitions = cursors.save.filter!(a => a.isDefinition); 277 if(!definitions.empty) return definitions.front; 278 279 auto canonicals = cursors.save.filter!(a => a.isCanonical); 280 if(!canonicals.empty) return canonicals.front; 281 282 assert(!cursors.empty); 283 return cursors.front; 284 } 285 286 return cursorFromCanonicals([lhs, rhs]); 287 } 288 289 290 291 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure { 292 import std.path: extension; 293 if(options.parseAsCpp) return true; 294 return headerFileName.extension != ".h"; 295 } 296 297 298 string getHeaderName(in const(char)[] line, in string[] includePaths) 299 @safe 300 { 301 const name = getHeaderName(line); 302 return name == "" ? name : fullPath(includePaths, name); 303 } 304 305 306 string getHeaderName(const(char)[] line) 307 @safe pure 308 { 309 import std.algorithm: startsWith, countUntil; 310 import std.range: dropBack; 311 import std.array: popFront; 312 import std..string: stripLeft; 313 314 line = line.stripLeft; 315 if(!line.startsWith(`#include `)) return ""; 316 317 const openingQuote = line.countUntil!(a => a == '"' || a == '<'); 318 const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1; 319 return line[openingQuote + 1 .. closingQuote].idup; 320 } 321 322 323 // transforms a header name, e.g. stdio.h 324 // into a full file path, e.g. /usr/include/stdio.h 325 private string fullPath(in string[] includePaths, in string headerName) @safe { 326 327 import std.algorithm: map, filter; 328 import std.path: buildPath, absolutePath; 329 import std.file: exists; 330 import std.conv: text; 331 import std.exception: enforce; 332 333 if(headerName.exists) return headerName; 334 335 auto filePaths = includePaths 336 .map!(a => buildPath(a, headerName).absolutePath) 337 .filter!exists; 338 339 enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'")); 340 341 return filePaths.front; 342 }