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