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, find, startsWith;
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             auto paths = includePaths.find!(p => include.startsWith(p));
185             if (paths.length)
186             {
187                 includesFile.writeln(`#include <`, include[paths[0].length+1..$], `>`);
188             }
189             else
190             {
191                 includesFile.writeln(`#include "`, include, `"`);
192             }
193             if(isCppHeader(options, include)) language = Language.Cpp;
194         }
195     }();
196 
197     /**
198        We remember the cursors already seen so as to not try and define
199        something twice (legal in C, illegal in D).
200     */
201     auto context = Context(options.dup, language);
202 
203     // parse all #includes at once and populate context with
204     // D definitions
205     expand(includesFileName, context, includePaths);
206 
207     context.fixNames;
208 
209     return TranslationText(moduleLines.join("\n"), context.translation);
210 }
211 
212 // write the original D code that doesn't need translating
213 private void writeUndefLines(in string inputFileName, ref from!"std.stdio".File outputFile)
214     @trusted
215 {
216     import std.stdio: File;
217     import std.algorithm: filter, canFind;
218 
219     auto lines = File(inputFileName).byLine.filter!(l => l.canFind("#undef"));
220     foreach(line; lines) {
221         outputFile.writeln(line);
222     }
223 }
224 
225 
226 // write the original D code that doesn't need translating
227 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
228     @trusted
229 {
230 
231     import dpp.expansion: getHeaderName;
232     import std.stdio: File;
233     import std.algorithm: filter;
234 
235     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
236         if(getHeaderName(line) == "") {
237             // not an #include directive, just pass through
238             outputFile.writeln(line);
239         }   // otherwise do nothing
240     }
241 }
242 
243 private bool isModuleLine(in const(char)[] line) @safe pure {
244     import std.string: stripLeft;
245     import std.algorithm: startsWith;
246     return line.stripLeft.startsWith("module ");
247 }
248 
249 private void runCPreProcessor(in string cppPath, in string tmpFileName, in string outputFileName) @safe {
250 
251     import std.exception: enforce;
252     import std.process: execute, executeShell, Config;
253     import std.conv: text;
254     import std.string: join, splitLines;
255     import std.stdio: File;
256     import std.algorithm: filter;
257     import std.regex: matchFirst;
258 
259     const cpp = cppPath == ""
260         ? (executeShell("clang-cpp --version").status == 0 ? "clang-cpp" : "cpp")
261         : cppPath;
262 
263     const cppArgs = [cpp, "-w", "--comments", tmpFileName];
264     const ret = () {
265         try {
266             string[string] env;
267             return execute(cppArgs, env, Config.stderrPassThrough);
268         } catch (Exception e)
269         {
270             import std.typecons : Tuple;
271             return Tuple!(int, "status", string, "output")(-1, e.msg);
272         }
273     }();
274     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
275 
276     {
277         auto outputFile = File(outputFileName, "w");
278         auto lines = ret.
279             output
280             .splitLines
281             .filter!(a => !a.matchFirst(`^\s*#`))
282             ;
283 
284         foreach(line; lines) {
285             outputFile.writeln(line);
286         }
287     }
288 }
289 
290 
291 string preamble(bool ignoreMacros) @safe pure {
292     import std.array: replace, join;
293     import std.algorithm: map, filter;
294     import std.string: splitLines;
295 
296     const vaArgs = ignoreMacros
297         ? ""
298         : "    #define __gnuc_va_list va_list\n\n" ~
299           "    #define __is_empty(_Type) dpp.isEmpty!(_Type)\n";
300 
301     return q{
302 
303         import core.stdc.config;
304         import core.stdc.stdarg: va_list;
305         static import core.simd;
306         static import std.conv;
307 
308         struct Int128 { long lower; long upper; }
309         struct UInt128 { ulong lower; ulong upper; }
310 
311         struct __locale_data { int dummy; }  // FIXME
312     } ~
313           vaArgs ~
314 
315           q{
316         alias _Bool = bool;
317 
318         struct dpp {
319 
320             static struct Opaque(int N) {
321                 void[N] bytes;
322             }
323 
324             // Replacement for the gcc/clang intrinsic
325             static bool isEmpty(T)() {
326                 return T.tupleof.length == 0;
327             }
328 
329             static struct Move(T) {
330                 T* ptr;
331             }
332 
333             // dmd bug causes a crash if T is passed by value.
334             // Works fine with ldc.
335             static auto move(T)(ref T value) {
336                 return Move!T(&value);
337             }
338 
339             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
340 
341                 private static string _memberMixinStr(string member) {
342                     import std.conv: text;
343                     import std.array: replace;
344                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
345                 }
346 
347                 private static string _enumMixinStr() {
348                     import std.array: join;
349 
350                     string[] ret;
351 
352                     ret ~= "enum " ~ name ~ "{";
353 
354                     static foreach(member; __traits(allMembers, T)) {
355                         ret ~= _memberMixinStr(member);
356                     }
357 
358                     ret ~= "}";
359 
360                     return ret.join("\n");
361                 }
362 
363                 mixin(_enumMixinStr());
364             }
365         }
366     }
367     .splitLines
368     .filter!(a => a != "")
369     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
370     .join("\n");
371 }