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 }