1 /** 2 Deals with expanding 3include directives inline. 3 */ 4 module dpp.expansion; 5 6 import dpp.from; 7 8 version(unittest) { 9 import unit_threaded: shouldEqual; 10 } 11 12 13 /** 14 If an #include directive, expand in place (add to context lines) 15 otherwise do nothing (add the line to the context) 16 */ 17 void maybeExpand(in string line, ref from!"dpp.runtime.context".Context context) 18 @safe 19 { 20 const headerName = getHeaderName(line); 21 22 if(headerName == "") 23 context.writeln(line); 24 else 25 expand(toFileName(context.options.includePaths, headerName), context); 26 } 27 28 29 @("translate no include") 30 @safe unittest { 31 import dpp.runtime.context: Context; 32 { 33 Context context; 34 maybeExpand("foo", context); 35 context.translation.shouldEqual("foo"); 36 } 37 { 38 Context context; 39 maybeExpand("bar", context); 40 context.translation.shouldEqual("bar"); 41 } 42 } 43 44 private string getHeaderName(string line) @safe pure { 45 import std.algorithm: startsWith, countUntil; 46 import std.range: dropBack; 47 import std.array: popFront; 48 import std..string: stripLeft; 49 50 line = line.stripLeft; 51 if(!line.startsWith(`#include `)) return ""; 52 53 const openingQuote = line.countUntil!(a => a == '"' || a == '<'); 54 const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1; 55 return line[openingQuote + 1 .. closingQuote]; 56 } 57 58 @("getHeaderFileName") 59 @safe pure unittest { 60 getHeaderName(`#include "foo.h"`).shouldEqual(`foo.h`); 61 getHeaderName(`#include "bar.h"`).shouldEqual(`bar.h`); 62 getHeaderName(`#include "foo.h" // comment`).shouldEqual(`foo.h`); 63 getHeaderName(`#include <foo.h>`).shouldEqual(`foo.h`); 64 getHeaderName(` #include "foo.h"`).shouldEqual(`foo.h`); 65 } 66 67 // transforms a header name, e.g. stdio.h 68 // into a full file path, e.g. /usr/include/stdio.h 69 private string toFileName(in string[] includePaths, in string headerName) @safe { 70 71 import std.algorithm: map, filter; 72 import std.path: buildPath, absolutePath; 73 import std.file: exists; 74 import std.conv: text; 75 import std.exception: enforce; 76 77 if(headerName.exists) return headerName; 78 79 auto filePaths = includePaths 80 .map!(a => buildPath(a, headerName).absolutePath) 81 .filter!exists; 82 83 enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'")); 84 85 return filePaths.front; 86 } 87 88 89 void expand(in string headerFileName, 90 ref from!"dpp.runtime.context".Context context, 91 in string file = __FILE__, 92 in size_t line = __LINE__) 93 @safe 94 { 95 import dpp.cursor.translation: translateTopLevelCursor; 96 import clang: parse, TranslationUnitFlags, Cursor; 97 import std.array: join, array; 98 import std.algorithm: sort, filter, map, chunkBy, any; 99 100 auto translationUnit = parse(headerFileName, 101 context.options.includePaths.map!(a => "-I" ~ a).array, 102 TranslationUnitFlags.DetailedPreprocessingRecord); 103 104 // In C there can be several declarations and one definition of a type. 105 // In D we can have only ever one of either. There might be multiple 106 // cursors in the translation unit that all refer to the same canonical type. 107 // Unfortunately, the canonical type is orthogonal to which cursor is the actual 108 // definition, so we prefer to find the definition if it exists, and if not, we 109 // take the canonical declaration so as to not repeat ourselves in D. 110 static Cursor trueCursor(R)(R cursors) { 111 112 if(cursors.save.any!(a => a.isDefinition)) 113 return cursors.filter!(a => a.isDefinition).front; 114 115 if(cursors.save.any!(a => a.isCanonical)) 116 return cursors.filter!(a => a.isCanonical).front; 117 118 assert(!cursors.empty); 119 return cursors.front; 120 } 121 122 static bool goodCursor(in Cursor cursor) { 123 return cursor.isCanonical || cursor.isDefinition; 124 } 125 126 auto cursors = () @trusted { 127 return translationUnit 128 .cursor 129 .children 130 // sort them by canonical cursor 131 .sort!((a, b) => a.canonical.sourceRange.start < 132 b.canonical.sourceRange.start) 133 // each chunk is a range of cursors representing the same canonical entity 134 .chunkBy!((a, b) => a.canonical == b.canonical) 135 // for each chunk, extract the one cursor we want 136 .map!trueCursor 137 // array is needed for sort 138 .array 139 // libclang gives us macros first, so we sort by line here 140 // (we also just messed up the order above as well) 141 .sort!((a, b) => a.sourceRange.start.line < b.sourceRange.start.line) 142 ; 143 }(); 144 145 const extern_ = isCppHeader(headerFileName) ? "extern(C++)" : "extern(C)"; 146 context.writeln([extern_, "{"]); 147 148 foreach(cursor; cursors) { 149 150 if(context.hasSeen(cursor)) continue; 151 context.rememberCursor(cursor); 152 153 const indentation = context.indentation; 154 const lines = translateTopLevelCursor(cursor, context, file, line); 155 if(lines.length) context.writeln(lines); 156 context.setIndentation(indentation); 157 } 158 159 context.writeln(["}", ""]); 160 } 161 162 private bool isCppHeader(in string headerFileName) @safe pure { 163 import std.path: extension; 164 return headerFileName.extension != ".h"; 165 }