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 250 struct Int128 { long lower; long upper; } 251 struct UInt128 { ulong lower; ulong upper; } 252 253 struct __locale_data { int dummy; } // FIXME 254 } ~ 255 " #define __gnuc_va_list va_list\n" ~ 256 " #define __is_empty(_Type) dpp.isEmpty!(_Type)\n" ~ 257 258 q{ 259 alias _Bool = bool; 260 261 struct dpp { 262 263 // Replacement for the gcc/clang intrinsic 264 static bool isEmpty(T)() { 265 return T.tupleof.length == 0; 266 } 267 268 static struct Move(T) { 269 T* ptr; 270 } 271 272 // FIXME - crashes if T is passed by value (which we want) 273 static auto move(T)(ref T value) { 274 return Move!T(&value); 275 } 276 277 278 mixin template EnumD(string name, T, string prefix) if(is(T == enum)) { 279 280 private static string _memberMixinStr(string member) { 281 import std.conv: text; 282 import std.array: replace; 283 return text(` `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`); 284 } 285 286 private static string _enumMixinStr() { 287 import std.array: join; 288 289 string[] ret; 290 291 ret ~= "enum " ~ name ~ "{"; 292 293 static foreach(member; __traits(allMembers, T)) { 294 ret ~= _memberMixinStr(member); 295 } 296 297 ret ~= "}"; 298 299 return ret.join("\n"); 300 } 301 302 mixin(_enumMixinStr()); 303 } 304 } 305 } 306 .splitLines 307 .filter!(a => a != "") 308 .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces 309 .join("\n"); 310 }