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!File(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 quasi-D 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 void preprocess(File)(in from!"dpp.runtime.options".Options options,
110                       in string inputFileName,
111                       in string outputFileName)
112 {
113     import std.file: remove;
114 
115     const tmpFileName = outputFileName ~ ".tmp";
116     scope(exit) if(!options.keepPreCppFiles) remove(tmpFileName);
117 
118     {
119         auto outputFile = File(tmpFileName, "w");
120 
121         const translationText = translationText!File(options, inputFileName);
122 
123         writeUndefLines(inputFileName, outputFile);
124 
125         outputFile.writeln(translationText.moduleDeclaration);
126         outputFile.writeln(preamble);
127         outputFile.writeln(translationText.dlangDeclarations);
128 
129         // write original D code
130         writeDlangLines(inputFileName, outputFile);
131     }
132 
133     runCPreProcessor(options.cppPath, tmpFileName, outputFileName);
134 }
135 
136 private struct TranslationText {
137     string moduleDeclaration;
138     string dlangDeclarations;
139 }
140 
141 // the translated D code from all #included files
142 private TranslationText translationText(File)(in from!"dpp.runtime.options".Options options,
143                                               in string inputFileName)
144 {
145 
146     import dpp.runtime.context: Context, Language;
147     version(dpp2)
148         import dpp2.expansion: expand, isCppHeader, getHeaderName;
149     else
150         import dpp.expansion: expand, isCppHeader, getHeaderName;
151     import clang.util: getTempFileName;
152 
153     import std.algorithm: map, filter;
154     import std..string: fromStringz;
155     import std.path: dirName;
156     import std.array: array, join;
157 
158     auto inputFile = File(inputFileName);
159     const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }();
160     auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }();
161     auto nonModuleLines = lines.filter!(a => !isModuleLine(a));
162     const includePaths = options.includePaths ~ inputFileName.dirName;
163     auto includes = nonModuleLines.map!(a => getHeaderName(a, includePaths)).filter!(a => a != "");
164     const includesFileName = getTempFileName();
165     auto language = Language.C;
166     // write a temporary file with all #included files in it
167     () @trusted {
168         auto includesFile = File(includesFileName, "w");
169         foreach(include; includes) {
170             includesFile.writeln(`#include "`, include, `"`);
171             if(isCppHeader(options, include)) language = Language.Cpp;
172         }
173     }();
174 
175     /**
176        We remember the cursors already seen so as to not try and define
177        something twice (legal in C, illegal in D).
178     */
179     auto context = Context(options.dup, language);
180 
181     // parse all #includes at once and populate context with
182     // D definitions
183     expand(includesFileName, context, includePaths);
184 
185     context.fixNames;
186 
187     return TranslationText(moduleLines.join("\n"), context.translation);
188 }
189 
190 // write the original D code that doesn't need translating
191 private void writeUndefLines(in string inputFileName, ref from!"std.stdio".File outputFile)
192     @trusted
193 {
194     import std.stdio: File;
195     import std.algorithm: filter, canFind;
196 
197     auto lines = File(inputFileName).byLine.filter!(l => l.canFind("#undef"));
198     foreach(line; lines) {
199         outputFile.writeln(line);
200     }
201 }
202 
203 
204 // write the original D code that doesn't need translating
205 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
206     @trusted
207 {
208 
209     import dpp.expansion: getHeaderName;
210     import std.stdio: File;
211     import std.algorithm: filter;
212 
213     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
214         if(getHeaderName(line) == "") {
215             // not an #include directive, just pass through
216             outputFile.writeln(line);
217         }   // otherwise do nothing
218     }
219 }
220 
221 private bool isModuleLine(in const(char)[] line) @safe pure {
222     import std..string: stripLeft;
223     import std.algorithm: startsWith;
224     return line.stripLeft.startsWith("module ");
225 }
226 
227 private void runCPreProcessor(in string cppPath, in string tmpFileName, in string outputFileName) @safe {
228 
229     import std.exception: enforce;
230     import std.process: execute, Config;
231     import std.conv: text;
232     import std..string: join, splitLines;
233     import std.stdio: File;
234     import std.algorithm: filter, startsWith;
235 
236     version (Windows)
237         enum cppDefault = "clang-cpp";
238     else
239         enum cppDefault = "cpp";
240 
241     const cpp = cppPath == "" ? cppDefault : cppPath;
242 
243     const cppArgs = [cpp, tmpFileName];
244     const ret = () {
245         try {
246             string[string] env;
247             return execute(cppArgs, env, Config.stderrPassThrough);
248         } catch (Exception e)
249         {
250             import std.typecons : Tuple;
251             return Tuple!(int, "status", string, "output")(-1, e.msg);
252         }
253     }();
254     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
255 
256     {
257         auto outputFile = File(outputFileName, "w");
258         auto lines = ret.
259             output
260             .splitLines
261             .filter!(a => !a.startsWith("#"))
262             ;
263 
264         foreach(line; lines) {
265             outputFile.writeln(line);
266         }
267     }
268 }
269 
270 
271 string preamble() @safe pure {
272     import std.array: replace, join;
273     import std.algorithm: map, filter;
274     import std..string: splitLines;
275 
276     return q{
277 
278         import core.stdc.config;
279         import core.stdc.stdarg: va_list;
280         static import core.simd;
281         static import std.conv;
282 
283         struct Int128 { long lower; long upper; }
284         struct UInt128 { ulong lower; ulong upper; }
285 
286         struct __locale_data { int dummy; }  // FIXME
287     } ~
288           "    #define __gnuc_va_list va_list\n\n" ~
289           "    #define __is_empty(_Type) dpp.isEmpty!(_Type)\n" ~
290 
291           q{
292         alias _Bool = bool;
293 
294         struct dpp {
295 
296             static struct Opaque(int N) {
297                 void[N] bytes;
298             }
299 
300             // Replacement for the gcc/clang intrinsic
301             static bool isEmpty(T)() {
302                 return T.tupleof.length == 0;
303             }
304 
305             static struct Move(T) {
306                 T* ptr;
307             }
308 
309             // dmd bug causes a crash if T is passed by value.
310             // Works fine with ldc.
311             static auto move(T)(ref T value) {
312                 return Move!T(&value);
313             }
314 
315             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
316 
317                 private static string _memberMixinStr(string member) {
318                     import std.conv: text;
319                     import std.array: replace;
320                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
321                 }
322 
323                 private static string _enumMixinStr() {
324                     import std.array: join;
325 
326                     string[] ret;
327 
328                     ret ~= "enum " ~ name ~ "{";
329 
330                     static foreach(member; __traits(allMembers, T)) {
331                         ret ~= _memberMixinStr(member);
332                     }
333 
334                     ret ~= "}";
335 
336                     return ret.join("\n");
337                 }
338 
339                 mixin(_enumMixinStr());
340             }
341         }
342     }
343     .splitLines
344     .filter!(a => a != "")
345     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
346     .join("\n");
347 }