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 }