1 module python.embedded;
2 import python.raw;
3 import std.exception: enforce;
4 //#include <python3.7m/pythonrun.h>
5 
6 /+
7 	Capabilities of pyd embedded interpreter:
8 		- run python code in D
9 		- run D code in python
10 		- declare python functions and use in D
11 		- access and manipulate python globals in D
12 		- wrap D classes/structs and use in python or D
13 		- wrap python class instances in D
14 		- wrap D ranges and iterate through them in python
15 		- wrap python iterators as D ranges
16 		- inheritance and multithreading
17 +/
18 
19 //import deimos.python.Python;
20 import python.raw : PyObject, PyFrameObject;
21 import python.type;
22 import std.algorithm: findSplit;
23 import std.string: strip, outdent;
24 import std.traits;
25 
26 char* zc()(string s) {
27 	    if(s.length && s[$-1] == 0) return s.dup.ptr;
28 		    return ((cast(char[])s) ~ "\0").ptr;
29 }
30 
31 PyObject* pyImportModule(string name)
32 {
33 	import std.string : toStringz;
34 	import std.exception : enforce;
35     enforce(pyIsInitialized(), "python not initialized");
36 	return PyImport_ImportModule(name.toStringz);
37 }
38 
39 bool pyIsInitialized()
40 {
41 	return true;
42 }
43 
44 
45 /+
46   Encapsulate a context within the Python interpreter.
47   This will preserve local variables and changes to the Python interpreter
48   made by 'from ___future__ import feature' across calls to this.py_eval and
49   this.py_stmts.
50   Otherwise, will not segregate global changes made to the Python interpreter.
51 +/
52 final class InterpContext
53 {
54     /// dict object: global variables in this context
55     PyObject* globals;
56     /// dict object: local variables in this context
57     PyObject* locals;
58     PyCompilerFlags flags;
59     PyFrameObject* frame;
60 
61     /**
62       */
63     this()
64 	{
65 		import std.string : toStringz;
66         enforce(pyIsInitialized(), "python not initialized");
67         locals = PyDict_New();
68 		globals = PyDict_New();
69 		PyDict_SetItemString(globals, "__builtins__",PyEval_GetBuiltins());
70     }
71 
72     void pushDummyFrame()
73 	{
74         auto threadstate = PyThreadState_Get();
75         if (threadstate.frame is null)
76 		{
77             PyCodeObject* code = PyCode_NewEmpty("<d>","<d>", 0);
78             frame = PyFrame_New(threadstate, code,
79                     cast(PyObject*)(globals),
80                     cast(PyObject*)(locals));
81             threadstate.frame = frame;
82         }
83     }
84 
85     void popDummyFrame()
86 	{
87         auto threadstate = PyThreadState_Get();
88         if(threadstate.frame == frame) {
89             threadstate.frame = null;
90         }
91     }
92 
93     /**
94       Evaluate a python expression once within this context and return the
95       result.
96 		Params:
97 			python = a python expression
98 		Returns:
99 			the result of expression
100      */
101     T py_eval(T = PyObject*)( string pythonExpression, string file = __FILE__, size_t line = __LINE__)
102 	{
103 		import std.string : toStringz;
104         auto pres = PyRun_StringFlags(
105                 pythonExpression.toStringz, 
106                 Py_eval_input,
107                 cast(PyObject*) globals,
108                 cast(PyObject*) locals,
109                 null); //&flags);
110         if(pres) {
111             return pres;
112         }else{
113 			import std.stdio;
114 			stderr.writefln("file: %s, line: %s",file,line);
115             //handle_exception(file,line);
116             assert(0);
117         }
118     }
119     /**
120       Evaluate one or more python statements once.
121 Params:
122 python = python statements
123      */
124     auto evalStatements(string pythonStatements, string file = __FILE__, size_t line = __LINE__)
125 	{
126         import std.string: outdent, toStringz;
127 
128         auto pres = PyRun_StringFlags(
129                 outdent(pythonStatements).toStringz,
130                 Py_file_input,
131                 cast(PyObject*) globals,
132                 cast(PyObject*) locals,
133                 &flags);
134         if(pres) {
135 			auto ret = pres.toString();
136 			pyDecRef(pres);
137 			return ret;
138         }else{
139             //handle_exception(file,line);
140 			assert(0);
141         }
142     }
143 
144     @property PyObject opDispatch(string id)() {
145         return this.locals[id];
146     }
147 
148     @property void opDispatch(string id, T)(T t) {
149         static if(is(T == PyObject)) {
150             alias t s;
151         }else{
152             PyObject s = py(t);
153         }
154         this.locals[id] = py(s);
155     }
156 }
157 
158 /++
159 Wraps a python function (specified as a string) as a D function roughly of
160 signature func_t
161 Params:
162 python = a python function
163 pythonModule = context in which to run expression. must be a python module name.
164 func_t = type of d function
165  +/
166 ReturnType!func_t py_def(func_t)(string pythonFunction, string pythonModule, ParameterTypeTuple!func_t args, string file = __FILE__, size_t line = __LINE__)
167 {
168 	import std.string : toStringz;
169 	alias ReturnType!func_t R;
170 	alias ParameterTypeTuple!func_t Args;
171     enum afterdef = findSplit(pythonFunction, "def")[2];
172     enum ereparen = findSplit(afterdef, "(")[0];
173     enum name = strip(ereparen) ~ "\0";
174     static PyObject func;
175     static PythonException exc;
176     static string errmsg;
177     static bool once = true;
178     enforce(pyIsInitialized(), "python not initialized");
179     if(once)
180 	{
181         once = false;
182         auto globals = py_import(pythonModule).getdict();
183         auto globals_ptr = pyIncRef(globals);
184         scope(exit) pyDecRef(globals_ptr);
185         auto locals = py((string[string]).init);
186         auto locals_ptr = pyIncRef(locals);
187         scope(exit) pyDecRef(locals_ptr);
188         if("__builtins__" !in globals)
189 		{
190 			globals = PyDict_New();
191 			PyDict_SetItemString(globals, "__builtins__",PyEval_GetBuiltins());
192         }
193         auto pres = PyRun_String(
194                     pythonFunction.toStringz,
195                     Py_file_input, globals_ptr, locals_ptr);
196         if(pres)
197 		{
198             auto res = new PyObject(pres);
199             func = locals[name];
200         } else
201 		{
202             try
203 			{
204                 handle_exception();
205             } catch(PythonException e)
206 			{
207                 exc = e;
208             }
209         }
210     }
211     if(!func)
212 	{
213         throw exc;
214     }
215     return func(args).to_d!R();
216 }
217 
218 /++
219 Evaluate a python expression once and return the result.
220 Params:
221 python = a python expression
222 pythonModule = context in which to run expression. either a python module name, or "".
223  +/
224 T evalExpression(T = PyObject*)(string pythonExpression, string pythonModule= "", string file = __FILE__, size_t line = __LINE__)
225 {
226 	import std.string : toStringz;
227     enforce(pyIsInitialized(), "python not initialized");
228     PyObject* locals = null;
229 	// FIXME
230 	pythonModule="";
231     locals = (pythonModule == "") ? PyDict_New() :  pyImportModule(pythonModule); // .getdict();
232 	pyIncRef(locals);
233     //if ("__builtins__" !in locals) {
234 		PyDict_SetItemString(locals, "__builtins__",PyEval_GetBuiltins());
235     //}
236     auto result = PyRun_StringFlags( pythonExpression.toStringz, Py_eval_input, locals, locals,null);
237 	scope(exit) Py_DecRef(locals);
238 	return result;
239 	/+
240 	//scope(exit) Py_XDecRef(locals);
241     if(result)
242 	{
243         //auto res = new PyObject(result);
244 		assert(0);
245         // return res.to_d!T();
246     }else
247 	{
248         // handle_exception(file,line);
249         assert(0);
250     } +/
251 }
252 
253 /++
254 Evaluate one or more python statements once.
255 Params:
256 python = python statements
257 modl = context in which to run expression. either a python module name, or "".
258  +/
259 auto evalStatements(string pythonStatements, string module_ = "",string file = __FILE__, size_t line = __LINE__)
260 {
261 	import std.string : toStringz;
262     enforce(pyIsInitialized(), "python not initialized");
263     PyObject* locals;
264     //PyObject* locals_ptr;
265     if (module_ == "") {
266         locals = PyDict_New();
267     }else {
268         //locals = pyImportModule(module_).getdict();
269     }
270     pyIncRef(locals);
271 	//locals_ptr = locals;
272 	// locals_ptr = pyIncRef(locals); // locals.ptr);
273     //if("__builtins__" !in locals) {
274         //auto builtins = new PyObject(PyEval_GetBuiltins());
275         // locals["__builtins__"] = builtins;
276     //}
277     auto pres = PyRun_StringFlags(
278             outdent(pythonStatements).toStringz,
279             Py_file_input, locals, locals,null);
280 	return pres; 
281 	/+
282     scope(exit) pyDecRef(locals);
283     if(pres) {
284         pyDecRef(pres);
285     }else{
286         //handle_exception(file,line);
287 		assert(0);
288     } +/
289 }
290 alias py_stmts = evalStatements;
291 
292 void evalSimpleString(string s)
293 {
294 	import std.string : toStringz;
295 	PyRun_SimpleStringFlags(s.toStringz,null);
296 }
297 void initializePython()
298 {
299 	Py_Initialize();
300 }
301 
302 string toString(PyObject* o)
303 {
304 	import std.string : fromStringz;
305 	char* s;
306 	PyArg_Parse(o, "s",&s);
307 	return s.fromStringz.idup;
308 }
309 
310 PyObject* getMember(PyObject* parent, string memberName)
311 {
312 	import std.string: toStringz;
313 	return PyObject_GetAttrString(parent,memberName.toStringz);
314 }
315 
316 PyObject* getModuleMember(string moduleName, string memberName)
317 {
318 	auto pmod = pyImportModule(moduleName);
319 	auto ret = getMember(pmod,memberName);
320 	Py_DecRef(pmod);
321 	return ret;
322 }
323 
324 PyObject* callObject(PyObject* o, PyObject* args)
325 {
326 	return null; // PyEval_CallObject(o,args);
327 }
328 
329 PyObject* construct(string moduleName, string className)
330 {
331 	auto pythonClass = getModuleMember(moduleName, className);
332 	auto pythonArgs = Py_BuildValue("()");
333 	auto result = callObject(pythonClass,pythonArgs);
334 	Py_DecRef(pythonClass);
335 	Py_DecRef(pythonArgs);
336 	return result;
337 }
338 
339 PyObject* pyBuildValue(Args...)(string formatString, Args args)
340 {
341 	import std.string : toStringz;
342 	return Py_BuildValue(formatString.toStringz,args);
343 }
344 
345 auto pyParseArgs(Args...)(PyObject* o, string formatString)
346 {
347 	import std.string : toStringz;
348 	import std.typecons: Tuple, tuple;
349 	import std.algorithm : map;
350 	import std.array : array;
351 	Tuple!Args args;
352 	auto argsPtr = args.map!(arg => &arg).array;
353 	PyArg_Parse(formatString,argsPtr);
354 	return args;
355 }
356