1 // SemiTwist Library
2 // Written in the D programming language.
3 
4 module semitwist.util.process;
5 
6 import std.conv;
7 import std.file;
8 import std.process;
9 import std.stream;
10 import std..string;
11 
12 version(Windows)
13 {
14 	import core.sys.windows.windows;
15 	extern(Windows) int CreatePipe(
16 		HANDLE* hReadPipe,
17 		HANDLE* hWritePipe,
18 		SECURITY_ATTRIBUTES* lpPipeAttributes,
19 		uint nSize);
20 }
21 else
22 {
23 	import core.sys.posix.unistd;
24 }
25 
26 import semitwist.util.all;
27 
28 void createPipe(out HANDLE readHandle, out HANDLE writeHandle)
29 {
30 	version(Windows)
31 	{
32 		auto secAttr = SECURITY_ATTRIBUTES(SECURITY_ATTRIBUTES.sizeof, null, true);
33 		if(!CreatePipe(&readHandle, &writeHandle, &secAttr, 0))
34 			throw new Exception("Couldn't create pipe");
35 	}
36 	else
37 	{
38 		int[2] pipeHandles;
39 		if(pipe(pipeHandles) != 0)
40 			throw new Exception("Couldn't create pipe");
41 		readHandle  = pipeHandles[0];
42 		writeHandle = pipeHandles[1];
43 	}
44 }
45 
46 bool evalCleanup = true;
47 
48 //TODO: Support string/wstring in addition to char[]/wchar[]
49 TRet eval(TRet)(string code, string imports="", string rdmdOpts="")
50 {
51 	void removeIfExists(string filename)
52 	{
53 		if(exists(filename))
54 			remove(filename);
55 	}
56 	void cleanup(string tempName)
57 	{
58 		if(evalCleanup)
59 		{
60 			removeIfExists(tempName~".d");
61 			removeIfExists(tempName~".d.deps");
62 			removeIfExists(tempName~".map");
63 			version(Windows)
64 				removeIfExists(tempName~".exe");
65 			else
66 				removeIfExists(tempName);
67 		}
68 	}
69 
70 	enum boilerplate = q{
71 		import std.conv;
72 		import std.process;
73 		import std.stream;
74 		import std..string;
75 		version(Windows) import std.c.windows.windows;
76 		%s
77 		alias %s TRet;
78 		void main(string[] args)
79 		{
80 			static if(is(TRet==void))
81 				_main();
82 			else
83 			{
84 				if(args.length < 2 || !std..string.isNumeric(args[1]))
85 					throw new Exception("First arg must be file handle for the return value");
86 
87 				auto writeHandle = cast(HANDLE)std.conv.to!size_t(args[1]);
88 				auto retValWriter = new std.stream.File(writeHandle, FileMode.Out);
89 
90 				auto ret = _main();
91 				retValWriter.write(ret);
92 			}
93 		}
94 		TRet _main()
95 		{
96 			%s
97 		}
98 	}.normalize();
99 	
100 	code = boilerplate.format(imports, TRet.stringof, code);
101 	
102 	HANDLE retValPipeRead;
103 	HANDLE retValPipeWrite;
104 	static if(!is(TRet==void))
105 	{
106 		createPipe(retValPipeRead, retValPipeWrite);
107 		auto retValReader = new std.stream.File(retValPipeRead, FileMode.In);
108 	}
109 	
110 	auto tempName = "eval_st_"~md5(code);
111 	std.file.write(tempName~".d", code);
112 	scope(exit) cleanup(tempName);
113 
114 	auto rdmdName = "rdmd";
115 
116 	auto errlvl = system(rdmdName~" -of"~tempName~" "~rdmdOpts~" "~tempName~" "~to!string(cast(size_t)retValPipeWrite));
117 	
118 	if(errlvl != 0)
119 		//TODO: Include failure text, and what part failed: compile or execution
120 		throw new Exception("eval failed");
121 	
122 	static if(is(TRet==void))
123 		return;
124 	else
125 	{
126 		TRet retVal;
127 		retValReader.read(retVal);
128 		return retVal;
129 	}
130 }
131 
132 mixin(unittestSemiTwistDLib(q{
133 	//enum test_eval1 = q{ eval!int(q{ writeln("Hello World!"); return 7; }, q{ import std.stdio; }) };
134 	//mixin(deferEnsure!(test_eval1, q{ _==7 }));
135 
136 	enum test_eval2 = q{ eval!int(q{ return 42; }) };
137 	mixin(deferEnsure!(test_eval2, q{ _==42 }));
138 
139 	enum test_eval3 = q{ eval!(char[])(q{ return "Test string".dup; }) };
140 	mixin(deferEnsure!(test_eval3, q{ _=="Test string" }));
141 
142 	enum test_eval4 = q{ eval!void(q{ return; }) };
143 	//mixin(deferEnsure!(test_eval4, q{ true })); //TODO: Fix error: "voids have no value"
144 	mixin(test_eval4~";");
145 }));