1 /* 2 Copyright: Martin Nowak 2013 - 3 License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 Authors: $(WEB code.dawg.eu, Martin Nowak) 5 */ 6 module drepl.engines.dmd; 7 import drepl.engines; 8 import std.algorithm, std.exception, std.file, std.path, std.process, std.range, std.stdio, std.string, std.typecons; 9 10 //------------------------------------------------------------------------------ 11 // tmpfile et. al. 12 13 // should be in core.stdc.stdlib 14 version (Posix) extern(C) char* mkdtemp(char* template_); 15 16 string mkdtemp() 17 { 18 version (Posix) 19 { 20 import core.stdc.string : strlen; 21 auto tmp = buildPath(tempDir(), "drepl.XXXXXX\0").dup; 22 auto dir = mkdtemp(tmp.ptr); 23 return dir[0 .. strlen(dir)].idup; 24 } 25 else 26 { 27 import std.format : format; 28 import std.random: uniform; 29 30 string path; 31 do 32 { 33 path = buildPath(tempDir(), format("drepl.%06X\0", uniform(0, 0xFFFFFF))); 34 } while (path.exists); 35 return path; 36 } 37 } 38 39 //------------------------------------------------------------------------------ 40 41 DMDEngine dmdEngine() 42 { 43 version(DigitalMars) 44 auto compiler = environment.get("DMD", "dmd"); 45 else version(LDC) 46 auto compiler = environment.get("DMD", "ldmd2"); 47 auto tmpDir = mkdtemp(); 48 return DMDEngine(compiler, tmpDir); 49 } 50 51 struct DMDEngine 52 { 53 string _compiler; 54 string _tmpDir; 55 size_t _id; 56 57 @disable this(this); 58 59 this(string compiler, string tmpDir) 60 { 61 _compiler = compiler; 62 _tmpDir = tmpDir; 63 if (_tmpDir.exists) rmdirRecurse(_tmpDir); 64 mkdirRecurse(_tmpDir); 65 } 66 67 ~this() 68 { 69 if (_tmpDir) rmdirRecurse(_tmpDir); 70 } 71 72 EngineResult evalDecl(in char[] decl) 73 { 74 auto m = newModule(); 75 m.f.writefln(q{ 76 // for public imports 77 public %1$s 78 79 extern(C) void _decls() 80 { 81 import std.algorithm, std.stdio; 82 writef("%%-(%%s, %%)", [__traits(allMembers, _mod%2$s)][1 .. $] 83 .filter!(d => !d.startsWith("_"))); 84 } 85 }.outdent(), decl, _id); 86 m.f.close(); 87 88 if (auto err = compileModule(m.path)) 89 return EngineResult(false, "", err); 90 91 ++_id; 92 93 auto func = cast(void function())loadFunc(m.path, "_decls"); 94 return captureOutput(func); 95 } 96 97 EngineResult evalExpr(in char[] expr) 98 { 99 auto m = newModule(); 100 m.f.writefln(q{ 101 extern(C) void _expr() 102 { 103 import std.stdio; 104 static if (is(typeof((() => (%1$s))()) == void)) 105 (%1$s), write("void"); 106 else 107 write((%1$s)); 108 } 109 }.outdent(), expr); 110 m.f.close(); 111 112 if (auto err = compileModule(m.path)) 113 return EngineResult(false, "", err); 114 115 ++_id; 116 117 auto func = cast(void function())loadFunc(m.path, "_expr"); 118 return captureOutput(func); 119 } 120 121 EngineResult evalStmt(in char[] stmt) 122 { 123 auto m = newModule(); 124 m.f.writefln(q{ 125 extern(C) void _run() 126 { 127 %s 128 } 129 }, stmt); 130 m.f.close(); 131 132 if (auto err = compileModule(m.path)) 133 return EngineResult(false, "", err); 134 135 ++_id; 136 137 auto func = cast(void function())loadFunc(m.path, "_run"); 138 return captureOutput(func); 139 } 140 141 private: 142 EngineResult captureOutput(void function() dg) 143 { 144 // TODO: cleanup, error checking... 145 import core.sys.posix.fcntl, core.sys.posix.unistd, std.conv : octal; 146 147 .stdout.flush(); 148 .stderr.flush(); 149 immutable 150 saveOut = dup(.stdout.fileno), 151 saveErr = dup(.stderr.fileno), 152 capOut = open(toStringz(_tmpDir~"/_stdout"), O_WRONLY|O_CREAT|O_TRUNC, octal!600), 153 capErr = open(toStringz(_tmpDir~"/_stderr"), O_WRONLY|O_CREAT|O_TRUNC, octal!600); 154 dup2(capOut, .stdout.fileno); 155 dup2(capErr, .stderr.fileno); 156 157 bool success = true; 158 try 159 { 160 dg(); 161 } 162 catch (Exception e) 163 { 164 success = false; 165 stderr.writeln(e.toString()); 166 } 167 .stdout.flush(); 168 .stderr.flush(); 169 close(.stdout.fileno); 170 close(.stderr.fileno); 171 dup2(saveOut, .stdout.fileno); 172 dup2(saveErr, .stderr.fileno); 173 close(saveOut); 174 close(saveErr); 175 return EngineResult( 176 success, readText(_tmpDir~"/_stdout"), readText(_tmpDir~"/_stderr")); 177 } 178 179 Tuple!(File, "f", string, "path") newModule() 180 { 181 auto path = buildPath(_tmpDir, format("_mod%s", _id)); 182 auto f = File(path~".d", "w"); 183 writeHeader(f); 184 return typeof(return)(f, path); 185 } 186 187 void writeHeader(ref File f) 188 { 189 if (_id > 0) 190 { 191 f.write("import _mod0"); 192 foreach (i; 1 .. _id) 193 f.writef(", _mod%s", i); 194 f.write(";"); 195 } 196 } 197 198 string compileModule(string path) 199 { 200 string[] linkerArgs; 201 final switch(_compiler) 202 { 203 case "dmd": 204 linkerArgs = ["-L-l:libphobos2.so"]; 205 break; 206 case "ldmd": 207 case "ldmd2": 208 linkerArgs = ["-L-l:libphobos2-ldc-shared.so"]; 209 break; 210 } 211 212 import std.regex: ctRegex, replaceAll; 213 auto args = [_compiler, "-I"~_tmpDir, "-of"~path~".so", "-fPIC", 214 "-shared", path] ~ linkerArgs; 215 foreach (i; 0 .. _id) 216 args ~= "-L"~_tmpDir~format("/_mod%s.so", i); 217 auto dmd = execute(args); 218 enum cleanErr = ctRegex!(`^.*Error: `, "m"); 219 if (dmd.status != 0) 220 return dmd.output.replaceAll(cleanErr, ""); 221 if (!exists(path~".so")) 222 return path~".so not found"; 223 return null; 224 } 225 226 void* loadFunc(string path, string name) 227 { 228 import core.runtime: Runtime; 229 import core.sys.posix.dlfcn: dlerror, dlsym; 230 231 auto lib = Runtime.loadLibrary(path~".so"); 232 if (lib is null) 233 { 234 auto msg = dlerror(); import core.stdc.string : strlen; 235 throw new Exception("failed to load "~path~".so ("~msg[0 .. strlen(msg)].idup~")"); 236 } 237 return dlsym(lib, toStringz(name)); 238 } 239 } 240 241 static assert(isEngine!DMDEngine); 242 243 unittest 244 { 245 alias ER = EngineResult; 246 247 DMDEngine dmd; 248 249 dmd = dmdEngine(); 250 assert(dmd.evalExpr("5+2") == ER(true, "7")); 251 assert(dmd.evalDecl("string foo() { return \"bar\"; }") == ER(true, "foo")); 252 assert(dmd.evalExpr("foo()") == ER(true, "bar")); 253 assert(!dmd.evalExpr("bar()").success); 254 255 assert(dmd.evalDecl("struct Foo { }") == ER(true, "Foo")); 256 assert(dmd.evalDecl("Foo f;") == ER(true, "f")); 257 assert(dmd.evalStmt("f = Foo();") == ER(true, "")); 258 259 dmd = dmdEngine(); 260 assert(dmd.evalDecl("void foo() {}") == ER(true, "foo")); 261 assert(dmd.evalExpr("foo()") == ER(true, "void")); 262 263 dmd = dmdEngine(); 264 assert(dmd.evalDecl("import std.stdio;").success); 265 assert(dmd.evalStmt("writeln(\"foo\");") == ER(true, "foo\n")); 266 } 267 268 unittest 269 { 270 alias ER = EngineResult; 271 auto dmd = dmdEngine(); 272 assert(dmd.evalDecl("void foo(int) {}") == ER(true, "foo")); 273 const auto er = dmd.evalStmt("foo(\"foo\");"); 274 assert(!er.success); 275 assert(er.stdout.empty); 276 assert(!er.stderr.empty); 277 }