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     const args = options.dlangCompiler ~ options.dlangCompilerArgs;
24     const res = execute(args);
25     enforce(res.status == 0, "Could not execute `" ~ args.join(" ") ~ "`:\n" ~ res.output);
26 
27     if(!options.keepDlangFiles) {
28         foreach(fileName; options.dFileNames)
29             remove(fileName);
30     }
31 }
32 
33 /**
34    Preprocesses a quasi-D file, expanding #include directives inline while
35    translating all definitions, and redefines any macros defined therein.
36 
37    The output is a valid D file that can be compiled.
38 
39    Params:
40         options = The runtime options.
41  */
42 void preprocess(File)(in from!"dpp.runtime.options".Options options,
43                       in string inputFileName,
44                       in string outputFileName)
45 {
46     import std.file: remove;
47 
48     const tmpFileName = outputFileName ~ ".tmp";
49     scope(exit) if(!options.keepPreCppFiles) remove(tmpFileName);
50 
51     {
52         auto outputFile = File(tmpFileName, "w");
53 
54         const translationText = translationText!File(options, inputFileName);
55 
56         outputFile.writeln(translationText.moduleDeclaration);
57         outputFile.writeln(preamble);
58         outputFile.writeln(translationText.dlangDeclarations);
59 
60         // write original D code
61         writeDlangLines(inputFileName, outputFile);
62     }
63 
64     runCPreProcessor(tmpFileName, outputFileName);
65 }
66 
67 private struct TranslationText {
68     string moduleDeclaration;
69     string dlangDeclarations;
70 }
71 
72 // the translated D code from all #included files
73 private TranslationText translationText(File)(in from!"dpp.runtime.options".Options options,
74                                               in string inputFileName)
75 {
76 
77     import dpp.runtime.context: Context, Language;
78     import dpp.expansion: expand, isCppHeader, getHeaderName;
79     import std.algorithm: map, filter;
80     import std..string: fromStringz;
81     import std.path: dirName;
82     import std.array: array, join;
83     import core.stdc.stdio: tmpnam;
84 
85     auto inputFile = File(inputFileName);
86     const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }();
87     auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }();
88     auto nonModuleLines = lines.filter!(a => !isModuleLine(a));
89     const includePaths = options.includePaths ~ inputFileName.dirName;
90     auto includes = nonModuleLines.map!(a => getHeaderName(a, includePaths)).filter!(a => a != "");
91     char[1024] tmpnamBuf;
92     const includesFileName = () @trusted { return cast(string) tmpnam(&tmpnamBuf[0]).fromStringz; }();
93     auto language = Language.C;
94     // write a temporary file with all #included files in it
95     () @trusted {
96         auto includesFile = File(includesFileName, "w");
97         foreach(include; includes) {
98             includesFile.writeln(`#include "`, include, `"`);
99             if(isCppHeader(options, include)) language = Language.Cpp;
100         }
101     }();
102 
103     /**
104        We remember the cursors already seen so as to not try and define
105        something twice (legal in C, illegal in D).
106     */
107     auto context = Context(options.indent, language);
108 
109     // parse all #includes at once and populate context with
110     // D definitions
111     expand(includesFileName, context, includePaths);
112 
113     context.fixNames;
114 
115     return TranslationText(moduleLines.join("\n"), context.translation);
116 }
117 
118 // write the original D code that doesn't need translating
119 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile)
120     @trusted
121 {
122 
123     import dpp.expansion: getHeaderName;
124     import std.stdio: File;
125     import std.algorithm: filter;
126 
127     foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) {
128         if(getHeaderName(line) == "")
129             // not an #include directive, just pass through
130             outputFile.writeln(line);
131         // otherwise do nothing
132     }
133 }
134 
135 bool isModuleLine(in const(char)[] line) @safe pure {
136     import std..string: stripLeft;
137     import std.algorithm: startsWith;
138     return line.stripLeft.startsWith("module ");
139 }
140 
141 
142 private void runCPreProcessor(in string tmpFileName, in string outputFileName) @safe {
143 
144     import std.exception: enforce;
145     import std.process: execute;
146     import std.conv: text;
147     import std..string: join, splitLines;
148     import std.stdio: File;
149     import std.algorithm: filter, startsWith;
150 
151     const cppArgs = ["cpp", tmpFileName];
152     const ret = execute(cppArgs);
153     enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output));
154 
155     {
156         auto outputFile = File(outputFileName, "w");
157         auto lines = ret.
158             output
159             .splitLines
160             .filter!(a => !a.startsWith("#"))
161             ;
162 
163         foreach(line; lines) {
164             outputFile.writeln(line);
165         }
166     }
167 
168 }
169 
170 
171 private string preamble() @safe pure {
172     import std.array: replace, join;
173     import std.algorithm: map, filter;
174     import std..string: splitLines;
175 
176     return q{
177 
178         import core.stdc.config;
179         import core.stdc.stdarg: va_list;
180         static import core.simd;
181 
182         struct Int128 { long lower; long upper; }
183         struct UInt128 { ulong lower; ulong upper; }
184 
185         struct __locale_data { int dummy; }  // FIXME
186     } ~
187         `    #define __gnuc_va_list va_list` ~ "\n" ~
188 
189           q{
190         alias _Bool = bool;
191 
192         struct dpp {
193 
194             static struct Move(T) {
195                 T* ptr;
196             }
197 
198             // FIXME - crashes if T is passed by value (which we want)
199             static auto move(T)(ref T value) {
200                 return Move!T(&value);
201             }
202 
203 
204             mixin template EnumD(string name, T, string prefix) if(is(T == enum)) {
205 
206                 private static string _memberMixinStr(string member) {
207                     import std.conv: text;
208                     import std.array: replace;
209                     return text(`    `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`);
210                 }
211 
212                 private static string _enumMixinStr() {
213                     import std.array: join;
214 
215                     string[] ret;
216 
217                     ret ~= "enum " ~ name ~ "{";
218 
219                     static foreach(member; __traits(allMembers, T)) {
220                         ret ~= _memberMixinStr(member);
221                     }
222 
223                     ret ~= "}";
224 
225                     return ret.join("\n");
226                 }
227 
228                 mixin(_enumMixinStr());
229             }
230         }
231     }
232     .splitLines
233     .filter!(a => a != "")
234     .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces
235     .join("\n");
236 }