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 
81         const args = ["-c", srcFileName];
82         const gccArgs = "g++" ~ args;
83         const clangArgs = "clang++" ~ args;
84 
85         scope(success) assert(exists(objFileName), objFileName ~ " was expected to exist but did not");
86 
87         const gccRet = execute(gccArgs);
88         if(gccRet.status == 0) return;
89 
90         const clangRet = execute(clangArgs);
91         if(clangRet.status != 0)
92             throw new Exception("Could not compile C++ boilerplate with either gcc or clang");
93     }
94 }
95 
96 
97 /**
98    Preprocesses a quasi-D file, expanding #include directives inline while
99    translating all definitions, and redefines any macros defined therein.
100 
101    The output is a valid D file that can be compiled.
102 
103    Params:
104         options = The runtime options.
105  */
106 void preprocess(File)(in from!"dpp.runtime.options".Options options,
107                       in string inputFileName,
108                       in string outputFileName)
109 {
110     import std.file: remove;
111 
112     const tmpFileName = outputFileName ~ ".tmp";
113     scope(exit) if(!options.keepPreCppFiles) remove(tmpFileName);
114 
115     {
116         auto outputFile = File(tmpFileName, "w");
117 
118         const translationText = translationText!File(options, inputFileName);
119 
120         outputFile.writeln(translationText.moduleDeclaration);
121         outputFile.writeln(preamble);
122         outputFile.writeln(translationText.dlangDeclarations);
123 
124         // write original D code
125         writeDlangLines(inputFileName, outputFile);
126     }
127 
128     runCPreProcessor(tmpFileName, outputFileName);
129 }
130 
131 private struct TranslationText {
132     string moduleDeclaration;
133     string dlangDeclarations;
134 }
135 
136 // the translated D code from all #included files
137 private TranslationText translationText(File)(in from!"dpp.runtime.options".Options options,
138                                               in string inputFileName)
139 {
140 
141     import dpp.runtime.context: Context, Language;
142     version(dpp2)
143         import dpp2.expansion: expand, isCppHeader, getHeaderName;
144     else
145         import dpp.expansion: expand, isCppHeader, getHeaderName;
146 
147     import std.algorithm: map, filter;
148     import std..string: fromStringz;
149     import std.path: dirName;
150     import std.array: array, join;
151 
152     auto inputFile = File(inputFileName);
153     const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }();
154     auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }();
155     auto nonModuleLines = lines.filter!(a => !isModuleLine(a));
156     const includePaths = options.includePaths ~ inputFileName.dirName;
157     auto includes = nonModuleLines.map!(a => getHeaderName(a, includePaths)).filter!(a => a != "");
158     const includesFileName = () @trusted {
159                                     import std.file: tempDir;
160                                     import core.sys.posix.stdlib: mkstemp;
161                                     char[] tmpnamBuf = tempDir() ~ "/dppXXXXXX\0".dup;
162                                     mkstemp(tmpnamBuf.ptr);
163                                     return tmpnamBuf[0 .. $ - 1].idup;
164                                 }();
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.indent, 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 writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
192     @trusted
193 {
194 
195     import dpp.expansion: getHeaderName;
196     import std.stdio: File;
197     import std.algorithm: filter;
198 
199     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
200         if(getHeaderName(line) == "")
201             // not an #include directive, just pass through
202             outputFile.writeln(line);
203         // otherwise do nothing
204     }
205 }
206 
207 bool isModuleLine(in const(char)[] line) @safe pure {
208     import std..string: stripLeft;
209     import std.algorithm: startsWith;
210     return line.stripLeft.startsWith("module ");
211 }
212 
213 
214 private void runCPreProcessor(in string tmpFileName, in string outputFileName) @safe {
215 
216     import std.exception: enforce;
217     import std.process: execute;
218     import std.conv: text;
219     import std..string: join, splitLines;
220     import std.stdio: File;
221     import std.algorithm: filter, startsWith;
222 
223     version (Windows)
224     {
225         enum cpp = "clang-cpp";
226     } else {
227         enum cpp = "cpp";
228     }
229 
230     const cppArgs = [cpp, tmpFileName];
231     const ret = () {
232         try
233         {
234             return execute(cppArgs);
235         }
236         catch (Exception e)
237         {
238             import std.typecons : Tuple;
239             return Tuple!(int, "status", string, "output")(-1, e.msg);
240         }
241     }();
242     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
243 
244     {
245         auto outputFile = File(outputFileName, "w");
246         auto lines = ret.
247             output
248             .splitLines
249             .filter!(a => !a.startsWith("#"))
250             ;
251 
252         foreach(line; lines) {
253             outputFile.writeln(line);
254         }
255     }
256 
257 }
258 
259 
260 string preamble() @safe pure {
261     import std.array: replace, join;
262     import std.algorithm: map, filter;
263     import std..string: splitLines;
264 
265     return q{
266 
267         import core.stdc.config;
268         import core.stdc.stdarg: va_list;
269         static import core.simd;
270         static import std.conv;
271 
272         struct Int128 { long lower; long upper; }
273         struct UInt128 { ulong lower; ulong upper; }
274 
275         struct __locale_data { int dummy; }  // FIXME
276     } ~
277           "    #define __gnuc_va_list va_list\n\n" ~
278           "    #define __is_empty(_Type) dpp.isEmpty!(_Type)\n" ~
279 
280           q{
281         alias _Bool = bool;
282 
283         struct dpp {
284 
285             static struct Opaque(int N) {
286                 void[N] bytes;
287             }
288 
289             // Replacement for the gcc/clang intrinsic
290             static bool isEmpty(T)() {
291                 return T.tupleof.length == 0;
292             }
293 
294             static struct Move(T) {
295                 T* ptr;
296             }
297 
298             // dmd bug causes a crash if T is passed by value.
299             // Works fine with ldc.
300             static auto move(T)(ref T value) {
301                 return Move!T(&value);
302             }
303 
304             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
305 
306                 private static string _memberMixinStr(string member) {
307                     import std.conv: text;
308                     import std.array: replace;
309                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
310                 }
311 
312                 private static string _enumMixinStr() {
313                     import std.array: join;
314 
315                     string[] ret;
316 
317                     ret ~= "enum " ~ name ~ "{";
318 
319                     static foreach(member; __traits(allMembers, T)) {
320                         ret ~= _memberMixinStr(member);
321                     }
322 
323                     ret ~= "}";
324 
325                     return ret.join("\n");
326                 }
327 
328                 mixin(_enumMixinStr());
329             }
330         }
331     }
332     .splitLines
333     .filter!(a => a != "")
334     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
335     .join("\n");
336 }