1 /**
2    Code to make the executable do what it does at runtime.
3  */
4 module dpp.runtime.app;
5 
6 import dpp.from;
7 
8 /**
9    The "real" main
10  */
11 void run(in from!"dpp.runtime.options".Options options) @safe {
12     import std.stdio: File;
13     import std.exception: enforce;
14     import std.process: execute;
15     import std.array: join;
16     import std.file: remove;
17 
18     foreach(dppFileName; options.dppFileNames)
19         preprocess(options, dppFileName, options.toDFileName(dppFileName));
20 
21     if(options.preprocessOnly) return;
22 
23     // See #102. We need some C++ boilerplate to link to the application.
24     scope(exit) if(options.cppStdLib) remove(CppBoilerplateCode.objFileName);
25     if(options.cppStdLib) CppBoilerplateCode.generate;
26     const cppExtraArgs = options.cppStdLib ? [CppBoilerplateCode.objFileName] : [];
27 
28     const args = options.dlangCompiler ~ options.dlangCompilerArgs ~ cppExtraArgs;
29     const res = execute(args);
30     enforce(res.status == 0, "Could not execute `" ~ args.join(" ") ~ "`:\n" ~ res.output);
31 
32     if(!options.keepDlangFiles) {
33         foreach(fileName; options.dFileNames)
34             remove(fileName);
35     }
36 }
37 
38 
39 // See #102. We need some C++ boilerplate to link to the application.
40 private struct CppBoilerplateCode {
41 
42     enum baseFileName = "cpp_boilerplate";
43     enum srcFileName = baseFileName ~ ".cpp";
44     version(Windows)
45         enum objFileName = baseFileName ~ ".obj";
46     else
47         enum objFileName = baseFileName ~ ".o";
48 
49 
50     static void generate() @safe {
51 
52         import std.stdio: File;
53         import std.file: remove;
54 
55         writeSrcFile;
56         scope(exit) remove(srcFileName);
57         compileSrcFile;
58     }
59 
60     ~this() @safe {
61         import std.file: remove;
62         remove(objFileName);
63     }
64 
65     static void writeSrcFile() @safe {
66         import std.stdio: File;
67 
68         auto file = File(srcFileName, "w");
69         file.writeln(`#include <vector>`);
70         file.writeln(`void cpp_stdlib_boilerplate_dpp() {`);
71         file.writeln(`    std::vector<bool> v;`);
72         file.writeln(`    (void) (v[0] == v[0]);`);
73         file.writeln(`    (void) (v.begin() == v.begin());`);
74         file.writeln(`}`);
75     }
76 
77     static void compileSrcFile() @safe {
78         import std.process: execute;
79         import std.file: exists;
80         import std.exception: enforce;
81 
82         const args = ["-c", srcFileName];
83         const gccArgs = "g++" ~ args;
84         const clangArgs = "clang++" ~ args;
85 
86         scope(success) enforce(objFileName.exists, objFileName ~ " was expected to exist but did not");
87 
88         const gccRet = execute(gccArgs);
89         if(gccRet.status == 0) return;
90 
91         const clangRet = execute(clangArgs);
92         if(clangRet.status != 0)
93             throw new Exception("Could not compile C++ boilerplate with either gcc or clang");
94     }
95 }
96 
97 
98 /**
99    Preprocesses a .dpp file, expanding #include directives inline while
100    translating all definitions, and redefines any macros defined therein.
101 
102    The output is a valid D file that can be compiled.
103 
104    Params:
105         options = The runtime options.
106         inputFileName = the name of the file to read
107         outputFileName = the name of the file to write
108  */
109 private void preprocess(in from!"dpp.runtime.options".Options options,
110                         in string inputFileName,
111                         in string outputFileName)
112     @safe
113 {
114     import std.file: remove, exists, mkdirRecurse, copy;
115     import std.stdio: File;
116     import std.path: dirName;
117 
118     const outputPath = outputFileName.dirName;
119     if(!outputPath.exists) mkdirRecurse(outputPath);
120 
121     const tmpFileName = outputFileName ~ ".tmp";
122     scope(exit) if(!options.keepPreCppFiles) remove(tmpFileName);
123 
124     {
125         auto outputFile = File(tmpFileName, "w");
126 
127         const translationText = translationText(options, inputFileName);
128 
129         writeUndefLines(inputFileName, outputFile);
130 
131         outputFile.writeln(translationText.moduleDeclaration);
132         outputFile.writeln(preamble(options.ignoreMacros));
133         outputFile.writeln(translationText.dlangDeclarations);
134 
135         // write original D code
136         writeDlangLines(inputFileName, outputFile);
137     }
138 
139     if(options.ignoreMacros)
140         copy(tmpFileName, outputFileName);
141     else
142         runCPreProcessor(options.cppPath, tmpFileName, outputFileName);
143 }
144 
145 private struct TranslationText {
146     string moduleDeclaration;
147     string dlangDeclarations;
148 }
149 
150 // the translated D code from all #included files
151 private TranslationText translationText(in from!"dpp.runtime.options".Options options,
152                                         in string inputFileName)
153     @safe
154 {
155 
156     import dpp.runtime.context: Context, Language;
157     version(dpp2)
158         import dpp2.expansion: expand, isCppHeader, getHeaderName;
159     else
160         import dpp.expansion: expand, isCppHeader, getHeaderName;
161     import clang.util: getTempFileName;
162 
163     import std.algorithm: map, filter;
164     import std..string: fromStringz;
165     import std.path: dirName;
166     import std.array: array, join;
167     import std.stdio: File;
168 
169     auto inputFile = File(inputFileName);
170     const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }();
171     auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }();
172     auto nonModuleLines = lines.filter!(a => !isModuleLine(a));
173     const includePaths = options.includePaths ~ inputFileName.dirName;
174     auto includes = nonModuleLines
175         .map!(a => getHeaderName(a, inputFileName, includePaths))
176         .filter!(a => a != "")
177         ;
178     const includesFileName = getTempFileName();
179     auto language = Language.C;
180     // write a temporary file with all #included files in it
181     () @trusted {
182         auto includesFile = File(includesFileName, "w");
183         foreach(include; includes) {
184             includesFile.writeln(`#include "`, include, `"`);
185             if(isCppHeader(options, include)) language = Language.Cpp;
186         }
187     }();
188 
189     /**
190        We remember the cursors already seen so as to not try and define
191        something twice (legal in C, illegal in D).
192     */
193     auto context = Context(options.dup, language);
194 
195     // parse all #includes at once and populate context with
196     // D definitions
197     expand(includesFileName, context, includePaths);
198 
199     context.fixNames;
200 
201     return TranslationText(moduleLines.join("\n"), context.translation);
202 }
203 
204 // write the original D code that doesn't need translating
205 private void writeUndefLines(in string inputFileName, ref from!"std.stdio".File outputFile)
206     @trusted
207 {
208     import std.stdio: File;
209     import std.algorithm: filter, canFind;
210 
211     auto lines = File(inputFileName).byLine.filter!(l => l.canFind("#undef"));
212     foreach(line; lines) {
213         outputFile.writeln(line);
214     }
215 }
216 
217 
218 // write the original D code that doesn't need translating
219 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
220     @trusted
221 {
222 
223     import dpp.expansion: getHeaderName;
224     import std.stdio: File;
225     import std.algorithm: filter;
226 
227     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
228         if(getHeaderName(line) == "") {
229             // not an #include directive, just pass through
230             outputFile.writeln(line);
231         }   // otherwise do nothing
232     }
233 }
234 
235 private bool isModuleLine(in const(char)[] line) @safe pure {
236     import std..string: stripLeft;
237     import std.algorithm: startsWith;
238     return line.stripLeft.startsWith("module ");
239 }
240 
241 private void runCPreProcessor(in string cppPath, in string tmpFileName, in string outputFileName) @safe {
242 
243     import std.exception: enforce;
244     import std.process: execute, executeShell, Config;
245     import std.conv: text;
246     import std..string: join, splitLines;
247     import std.stdio: File;
248     import std.algorithm: filter, startsWith;
249 
250     const cpp = cppPath == ""
251         ? (executeShell("clang-cpp --version").status == 0 ? "clang-cpp" : "cpp")
252         : cppPath;
253 
254     const cppArgs = [cpp, "-w", "--comments", tmpFileName];
255     const ret = () {
256         try {
257             string[string] env;
258             return execute(cppArgs, env, Config.stderrPassThrough);
259         } catch (Exception e)
260         {
261             import std.typecons : Tuple;
262             return Tuple!(int, "status", string, "output")(-1, e.msg);
263         }
264     }();
265     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
266 
267     {
268         auto outputFile = File(outputFileName, "w");
269         auto lines = ret.
270             output
271             .splitLines
272             .filter!(a => !a.startsWith("#"))
273             ;
274 
275         foreach(line; lines) {
276             outputFile.writeln(line);
277         }
278     }
279 }
280 
281 
282 string preamble(bool ignoreMacros) @safe pure {
283     import std.array: replace, join;
284     import std.algorithm: map, filter;
285     import std..string: splitLines;
286 
287     const vaArgs = ignoreMacros
288         ? ""
289         : "    #define __gnuc_va_list va_list\n\n" ~
290           "    #define __is_empty(_Type) dpp.isEmpty!(_Type)\n";
291 
292     return q{
293 
294         import core.stdc.config;
295         import core.stdc.stdarg: va_list;
296         static import core.simd;
297         static import std.conv;
298 
299         struct Int128 { long lower; long upper; }
300         struct UInt128 { ulong lower; ulong upper; }
301 
302         struct __locale_data { int dummy; }  // FIXME
303     } ~
304           vaArgs ~
305 
306           q{
307         alias _Bool = bool;
308 
309         struct dpp {
310 
311             static struct Opaque(int N) {
312                 void[N] bytes;
313             }
314 
315             // Replacement for the gcc/clang intrinsic
316             static bool isEmpty(T)() {
317                 return T.tupleof.length == 0;
318             }
319 
320             static struct Move(T) {
321                 T* ptr;
322             }
323 
324             // dmd bug causes a crash if T is passed by value.
325             // Works fine with ldc.
326             static auto move(T)(ref T value) {
327                 return Move!T(&value);
328             }
329 
330             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
331 
332                 private static string _memberMixinStr(string member) {
333                     import std.conv: text;
334                     import std.array: replace;
335                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
336                 }
337 
338                 private static string _enumMixinStr() {
339                     import std.array: join;
340 
341                     string[] ret;
342 
343                     ret ~= "enum " ~ name ~ "{";
344 
345                     static foreach(member; __traits(allMembers, T)) {
346                         ret ~= _memberMixinStr(member);
347                     }
348 
349                     ret ~= "}";
350 
351                     return ret.join("\n");
352                 }
353 
354                 mixin(_enumMixinStr());
355             }
356         }
357     }
358     .splitLines
359     .filter!(a => a != "")
360     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
361     .join("\n");
362 }