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     import core.stdc.stdio: tmpnam;
152 
153     auto inputFile = File(inputFileName);
154     const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }();
155     auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }();
156     auto nonModuleLines = lines.filter!(a => !isModuleLine(a));
157     const includePaths = options.includePaths ~ inputFileName.dirName;
158     auto includes = nonModuleLines.map!(a => getHeaderName(a, includePaths)).filter!(a => a != "");
159     char[1024] tmpnamBuf;
160     const includesFileName = () @trusted { return cast(string) tmpnam(&tmpnamBuf[0]).fromStringz; }();
161     auto language = Language.C;
162     // write a temporary file with all #included files in it
163     () @trusted {
164         auto includesFile = File(includesFileName, "w");
165         foreach(include; includes) {
166             includesFile.writeln(`#include "`, include, `"`);
167             if(isCppHeader(options, include)) language = Language.Cpp;
168         }
169     }();
170 
171     /**
172        We remember the cursors already seen so as to not try and define
173        something twice (legal in C, illegal in D).
174     */
175     auto context = Context(options.indent, language);
176 
177     // parse all #includes at once and populate context with
178     // D definitions
179     expand(includesFileName, context, includePaths);
180 
181     context.fixNames;
182 
183     return TranslationText(moduleLines.join("\n"), context.translation);
184 }
185 
186 // write the original D code that doesn't need translating
187 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
188     @trusted
189 {
190 
191     import dpp.expansion: getHeaderName;
192     import std.stdio: File;
193     import std.algorithm: filter;
194 
195     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
196         if(getHeaderName(line) == "")
197             // not an #include directive, just pass through
198             outputFile.writeln(line);
199         // otherwise do nothing
200     }
201 }
202 
203 bool isModuleLine(in const(char)[] line) @safe pure {
204     import std.string: stripLeft;
205     import std.algorithm: startsWith;
206     return line.stripLeft.startsWith("module ");
207 }
208 
209 
210 private void runCPreProcessor(in string tmpFileName, in string outputFileName) @safe {
211 
212     import std.exception: enforce;
213     import std.process: execute;
214     import std.conv: text;
215     import std.string: join, splitLines;
216     import std.stdio: File;
217     import std.algorithm: filter, startsWith;
218 
219     const cppArgs = ["cpp", tmpFileName];
220     const ret = execute(cppArgs);
221     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
222 
223     {
224         auto outputFile = File(outputFileName, "w");
225         auto lines = ret.
226             output
227             .splitLines
228             .filter!(a => !a.startsWith("#"))
229             ;
230 
231         foreach(line; lines) {
232             outputFile.writeln(line);
233         }
234     }
235 
236 }
237 
238 
239 string preamble() @safe pure {
240     import std.array: replace, join;
241     import std.algorithm: map, filter;
242     import std.string: splitLines;
243 
244     return q{
245 
246         import core.stdc.config;
247         import core.stdc.stdarg: va_list;
248         static import core.simd;
249         static import std.conv;
250 
251         struct Int128 { long lower; long upper; }
252         struct UInt128 { ulong lower; ulong upper; }
253 
254         struct __locale_data { int dummy; }  // FIXME
255     } ~
256           "    #define __gnuc_va_list va_list\n\n" ~
257           "    #define __is_empty(_Type) dpp.isEmpty!(_Type)\n" ~
258 
259           q{
260         alias _Bool = bool;
261 
262         struct dpp {
263 
264             static struct Opaque(int N) {
265                 void[N] bytes;
266             }
267 
268             // Replacement for the gcc/clang intrinsic
269             static bool isEmpty(T)() {
270                 return T.tupleof.length == 0;
271             }
272 
273             static struct Move(T) {
274                 T* ptr;
275             }
276 
277             // FIXME - crashes if T is passed by value (which we want)
278             static auto move(T)(ref T value) {
279                 return Move!T(&value);
280             }
281 
282 
283             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
284 
285                 private static string _memberMixinStr(string member) {
286                     import std.conv: text;
287                     import std.array: replace;
288                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
289                 }
290 
291                 private static string _enumMixinStr() {
292                     import std.array: join;
293 
294                     string[] ret;
295 
296                     ret ~= "enum " ~ name ~ "{";
297 
298                     static foreach(member; __traits(allMembers, T)) {
299                         ret ~= _memberMixinStr(member);
300                     }
301 
302                     ret ~= "}";
303 
304                     return ret.join("\n");
305                 }
306 
307                 mixin(_enumMixinStr());
308             }
309         }
310     }
311     .splitLines
312     .filter!(a => a != "")
313     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
314     .join("\n");
315 }