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 }