1 /** 2 Deals with expanding #include directives inline. 3 */ 4 module dpp2.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.runtime.context: Language; 25 import dpp2.translation.node: translate; 26 import clang: parse; 27 28 const extern_ = context.language == Language.Cpp ? "extern(C++)" : "extern(C)"; 29 context.writeln([extern_, "{"]); 30 31 const translationUnit = parseTU(translUnitFileName, context); 32 const topLevelCursors = translationUnit.cursor.children; 33 auto nodes = cursorsToNodes(topLevelCursors); 34 35 foreach(node; nodes) { 36 const indentation = context.indentation; 37 const lines = translate(node); 38 if(lines.length) context.writeln(lines); 39 context.setIndentation(indentation); 40 } 41 42 context.writeln(["}", ""]); 43 context.writeln(""); 44 } 45 46 47 // returns a range of dpp2.sea.Node 48 private auto cursorsToNodes(in from!"clang".Cursor[] cursors) @safe { 49 import dpp2.transform: toNode; 50 import clang: Cursor; 51 import std.algorithm: map, filter; 52 53 return cursors 54 .filter!(c => c.kind == Cursor.Kind.StructDecl) 55 .map!toNode 56 ; 57 } 58 59 private from!"clang".TranslationUnit parseTU( 60 in string translUnitFileName, 61 ref from!"dpp.runtime.context".Context context, 62 ) 63 @safe 64 { 65 import clang: parse, TranslationUnitFlags; 66 const args = clangArgs(context, translUnitFileName); 67 return parse(translUnitFileName, 68 args, 69 TranslationUnitFlags.DetailedPreprocessingRecord); 70 } 71 72 string[] clangArgs(in from!"dpp.runtime.context".Context context, 73 in string inputFileName) 74 @safe pure 75 { 76 import dpp.runtime.context: Language; 77 import std.algorithm: map; 78 import std.range: chain; 79 import std.array: array; 80 81 auto args = 82 chain( 83 includePaths(context.options, inputFileName).map!(a => "-I" ~ a), 84 context.options.defines.map!(a => "-D" ~ a) 85 ).array; 86 87 if(context.options.parseAsCpp || context.language == Language.Cpp) 88 args ~= ["-xc++", "-std=c++14"]; 89 else 90 args ~= "-xc"; 91 92 return args; 93 } 94 95 96 string[] includePaths(in from!"dpp.runtime.options".Options options, 97 in string inputFileName) 98 @safe pure 99 { 100 import std.path: dirName; 101 return options.includePaths.dup ~ inputFileName.dirName; 102 } 103 104 105 bool isCppHeader(in from!"dpp.runtime.options".Options options, in string headerFileName) @safe pure { 106 import std.path: extension; 107 if(options.parseAsCpp) return true; 108 return headerFileName.extension != ".h"; 109 } 110 111 112 string getHeaderName(in const(char)[] line, in string[] includePaths) 113 @safe 114 { 115 const name = getHeaderName(line); 116 return name == "" ? name : fullPath(includePaths, name); 117 } 118 119 120 string getHeaderName(const(char)[] line) 121 @safe pure 122 { 123 import std.algorithm: startsWith, countUntil; 124 import std.range: dropBack; 125 import std.array: popFront; 126 import std..string: stripLeft; 127 128 line = line.stripLeft; 129 if(!line.startsWith(`#include `)) return ""; 130 131 const openingQuote = line.countUntil!(a => a == '"' || a == '<'); 132 const closingQuote = line[openingQuote + 1 .. $].countUntil!(a => a == '"' || a == '>') + openingQuote + 1; 133 return line[openingQuote + 1 .. closingQuote].idup; 134 } 135 136 137 // transforms a header name, e.g. stdio.h 138 // into a full file path, e.g. /usr/include/stdio.h 139 private string fullPath(in string[] includePaths, in string headerName) @safe { 140 141 import std.algorithm: map, filter; 142 import std.path: buildPath, absolutePath; 143 import std.file: exists; 144 import std.conv: text; 145 import std.exception: enforce; 146 147 if(headerName.exists) return headerName; 148 149 auto filePaths = includePaths 150 .map!(a => buildPath(a, headerName).absolutePath) 151 .filter!exists; 152 153 enforce(!filePaths.empty, text("d++ cannot find file path for header '", headerName, "'")); 154 155 return filePaths.front; 156 }