1 |
|
/* This is a component of LinuxCNC |
2 |
|
* Copyright 2011, 2012 Michael Haberler <git@mah.priv.at> |
3 |
|
* |
4 |
|
* This program is free software; you can redistribute it and/or modify |
5 |
|
* it under the terms of the GNU General Public License as published by |
6 |
|
* the Free Software Foundation; either version 2 of the License, or |
7 |
|
* (at your option) any later version. |
8 |
|
* |
9 |
|
* This program is distributed in the hope that it will be useful, |
10 |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 |
|
* GNU General Public License for more details. |
13 |
|
* |
14 |
|
* You should have received a copy of the GNU General Public License |
15 |
|
* along with this program; if not, write to the Free Software |
16 |
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
17 |
|
*/ |
18 |
|
// Support for embedding Python in the RS274NGC interpreter |
19 |
|
// with access to Interp and Canon |
20 |
|
// |
21 |
|
// NB: all this is executed at readahead time |
22 |
|
// |
23 |
|
// Michael Haberler 4/2011 |
24 |
|
// |
25 |
|
// if you get a segfault like described |
26 |
|
// here: https://bugs.launchpad.net/ubuntu/+source/mesa/+bug/259219 |
27 |
|
// or here: https://www.libavg.de/wiki/LinuxInstallIssues#glibc_invalid_pointer : |
28 |
|
// |
29 |
|
// try this before starting milltask and axis in emc: |
30 |
|
// LD_PRELOAD=/usr/lib/libstdc++.so.6 $EMCTASK ... |
31 |
|
// LD_PRELOAD=/usr/lib/libstdc++.so.6 $EMCDISPLAY ... |
32 |
|
// |
33 |
|
// this is actually a bug in libgl1-mesa-dri and it looks |
34 |
|
// it has been fixed in mesa - 7.10.1-0ubuntu2 |
35 |
|
|
36 |
|
#include "python_plugin.hh" |
37 |
|
#include "interp_python.hh" |
38 |
|
#include <boost/python/extract.hpp> |
39 |
|
#include <boost/python/import.hpp> |
40 |
|
#include <boost/python/str.hpp> |
41 |
|
namespace bp = boost::python; |
42 |
|
|
43 |
|
#include <unistd.h> |
44 |
|
#include <stdio.h> |
45 |
|
#include <stdlib.h> |
46 |
|
#include <math.h> |
47 |
|
#include <string.h> |
48 |
|
#include <ctype.h> |
49 |
|
#include <sys/types.h> |
50 |
|
#include <sys/stat.h> |
51 |
|
#include <exception> |
52 |
|
|
53 |
|
#include "rs274ngc.hh" |
54 |
|
#include "interp_return.hh" |
55 |
|
#include "interp_internal.hh" |
56 |
|
#include "rs274ngc_interp.hh" |
57 |
|
#include "units.h" |
58 |
|
|
59 |
|
extern PythonPlugin *python_plugin; |
60 |
|
|
61 |
|
#define PYCHK(bad, fmt, ...) \ |
62 |
|
do { \ |
63 |
|
if (bad) { \ |
64 |
|
logPy(fmt, ## __VA_ARGS__); \ |
65 |
|
ERM(fmt, ## __VA_ARGS__); \ |
66 |
|
goto error; \ |
67 |
|
} \ |
68 |
|
} while(0) |
69 |
|
|
70 |
|
#define IS_STRING(x) (PyObject_IsInstance(x.ptr(), (PyObject*)&PyString_Type)) |
71 |
|
#define IS_INT(x) (PyObject_IsInstance(x.ptr(), (PyObject*)&PyInt_Type)) |
72 |
|
|
73 |
|
// decode a Python exception into a string. |
74 |
2 |
std::string handle_pyerror() |
75 |
|
{ |
76 |
|
using namespace boost::python; |
77 |
|
using namespace boost; |
78 |
|
|
79 |
|
PyObject *exc,*val,*tb; |
80 |
2 |
object formatted_list, formatted; |
81 |
2 |
PyErr_Fetch(&exc,&val,&tb); |
82 |
6 |
handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb)); |
83 |
4 |
object traceback(import("traceback")); |
84 |
2 |
if (!tb) { |
85 |
4 |
object format_exception_only(traceback.attr("format_exception_only")); |
86 |
4 |
formatted_list = format_exception_only(hexc,hval); |
87 |
|
} else { |
88 |
|
object format_exception(traceback.attr("format_exception")); |
89 |
|
formatted_list = format_exception(hexc,hval,htb); |
90 |
|
} |
91 |
6 |
formatted = str("\n").join(formatted_list); |
92 |
8 |
return extract<std::string>(formatted); |
93 |
|
} |
94 |
|
|
95 |
|
int Interp::py_reload() |
96 |
|
{ |
97 |
|
if (PYUSABLE) { |
98 |
|
CHKS((python_plugin->initialize() == PLUGIN_EXCEPTION), |
99 |
|
"py_reload:\n%s", python_plugin->last_exception().c_str()); |
100 |
|
} |
101 |
|
return INTERP_OK; |
102 |
|
} |
103 |
|
|
104 |
|
// determine wether [module.]funcname is callable |
105 |
363 |
bool Interp::is_pycallable(setup_pointer settings, |
106 |
|
const char *module, |
107 |
|
const char *funcname) |
108 |
|
{ |
109 |
363 |
if (!PYUSABLE) |
110 |
|
return false; |
111 |
|
|
112 |
363 |
return python_plugin->is_callable(module,funcname); |
113 |
|
} |
114 |
|
|
115 |
|
// all parameters to/results from Python calls go through the callframe, which looks a bit awkward |
116 |
|
// the reason is not to expose boost.python through the interpreter public interface |
117 |
7 |
int Interp::pycall(setup_pointer settings, |
118 |
|
context_pointer frame, |
119 |
|
const char *module, |
120 |
|
const char *funcname, |
121 |
|
int calltype) |
122 |
|
{ |
123 |
7 |
bp::object retval, function; |
124 |
7 |
std::string msg; |
125 |
7 |
bool py_exception = false; |
126 |
7 |
int status = INTERP_OK; |
127 |
|
PyObject *res_str; |
128 |
|
|
129 |
7 |
if (_setup.loggingLevel > 4) |
130 |
|
logPy("pycall(%s.%s) \n", module ? module : "", funcname); |
131 |
|
|
132 |
7 |
CHKS(!PYUSABLE, "pycall(%s): Pyhton plugin not initialized",funcname); |
133 |
7 |
frame->pystuff.impl->py_return_type = 0; |
134 |
|
|
135 |
|
switch (calltype) { |
136 |
|
case PY_EXECUTE: // just run a string |
137 |
|
python_plugin->run_string(funcname, retval); |
138 |
|
CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, |
139 |
|
"run_string(%s):\n%s", funcname, |
140 |
|
python_plugin->last_exception().c_str()); |
141 |
|
break; |
142 |
|
// default: |
143 |
|
// switch (frame->entry_at) { //FIXTHIS terminally ugly |
144 |
|
case PY_FINISH_OWORDCALL: |
145 |
|
case PY_FINISH_PROLOG: |
146 |
|
case PY_FINISH_BODY: |
147 |
|
case PY_FINISH_EPILOG: |
148 |
|
logPy("pycall: call generator.next()" ); |
149 |
|
|
150 |
|
// handler continuation if a generator was used |
151 |
|
try { |
152 |
|
retval = frame->pystuff.impl->generator_next(); |
153 |
|
} |
154 |
|
catch (bp::error_already_set) { |
155 |
|
if (PyErr_Occurred()) { |
156 |
|
// StopIteration is raised when the generator executes 'return' |
157 |
|
// instead of another 'yield INTERP_EXECUTE_FINISH |
158 |
|
// Technically this means a normal end of the handler and hence we |
159 |
|
// treat it as INTERP_OK indicating this handler is now done |
160 |
|
if (PyErr_ExceptionMatches(PyExc_StopIteration)) { |
161 |
|
frame->pystuff.impl->py_return_type = RET_STOPITERATION; |
162 |
|
bp::handle_exception(); |
163 |
|
PyErr_Clear(); |
164 |
|
logPy("pycall: call generator - StopIteration exception"); |
165 |
|
return INTERP_OK; |
166 |
|
} else { |
167 |
|
msg = handle_pyerror(); |
168 |
|
bp::handle_exception(); |
169 |
|
PyErr_Clear(); |
170 |
|
logPy("pycall: call generator - exception: %s",msg.c_str()); |
171 |
|
|
172 |
|
ERS("exception during generator call: %s", msg.c_str()); |
173 |
|
} |
174 |
|
} else |
175 |
|
Error("calling generator: duh"); |
176 |
|
} |
177 |
|
break; |
178 |
|
default: |
179 |
35 |
python_plugin->call(module,funcname, frame->pystuff.impl->tupleargs,frame->pystuff.impl->kwargs,retval); |
180 |
7 |
CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, |
181 |
|
"pycall(%s):\n%s", funcname, |
182 |
|
python_plugin->last_exception().c_str()); |
183 |
|
} |
184 |
|
|
185 |
|
try { |
186 |
7 |
status = INTERP_OK; |
187 |
|
|
188 |
7 |
switch (calltype) { |
189 |
|
case PY_OWORDCALL: |
190 |
|
case PY_PROLOG: |
191 |
|
case PY_BODY: |
192 |
|
case PY_EPILOG: |
193 |
|
case PY_FINISH_OWORDCALL: |
194 |
|
case PY_FINISH_PROLOG: |
195 |
|
case PY_FINISH_BODY: |
196 |
|
case PY_FINISH_EPILOG: |
197 |
|
|
198 |
|
// these may return values in several flavours: |
199 |
|
// - an int (INTERP_OK, INTERP_ERROR, INTERP_EXECUTE_FINISH...) |
200 |
|
// - a double (Python oword subroutine) |
201 |
|
// - a generator object, in case the handler contained a yield statement |
202 |
7 |
if (retval.ptr() != Py_None) { |
203 |
3 |
if (PyGen_Check(retval.ptr())) { |
204 |
|
|
205 |
|
// a generator was returned. This must have been the first time call to a handler |
206 |
|
// which contains a yield. Extract next() method. |
207 |
|
frame->pystuff.impl->generator_next = bp::getattr(retval, "next"); |
208 |
|
|
209 |
|
// and call it for the first time. |
210 |
|
// Expect execution up to first 'yield INTERP_EXECUTE_FINISH'. |
211 |
|
frame->pystuff.impl->py_returned_int = bp::extract<int>(frame->pystuff.impl->generator_next()); |
212 |
|
frame->pystuff.impl->py_return_type = RET_YIELD; |
213 |
|
|
214 |
3 |
} else if (PyString_Check(retval.ptr())) { |
215 |
|
// returning a string sets the interpreter error message and aborts |
216 |
|
char *msg = bp::extract<char *>(retval); |
217 |
|
ERM("%s", msg); |
218 |
|
frame->pystuff.impl->py_return_type = RET_ERRORMSG; |
219 |
|
status = INTERP_ERROR; |
220 |
3 |
} else if (PyInt_Check(retval.ptr())) { |
221 |
|
frame->pystuff.impl->py_returned_int = bp::extract<int>(retval); |
222 |
|
frame->pystuff.impl->py_return_type = RET_INT; |
223 |
|
logPy("Python call %s.%s returned int: %d", module, funcname, frame->pystuff.impl->py_returned_int); |
224 |
3 |
} else if (PyFloat_Check(retval.ptr())) { |
225 |
9 |
frame->pystuff.impl->py_returned_double = bp::extract<double>(retval); |
226 |
3 |
frame->pystuff.impl->py_return_type = RET_DOUBLE; |
227 |
3 |
logPy("Python call %s.%s returned float: %f", module, funcname, frame->pystuff.impl->py_returned_double); |
228 |
|
} else { |
229 |
|
// not a generator, int, or float - strange |
230 |
|
PyObject *res_str = PyObject_Str(retval.ptr()); |
231 |
|
Py_XDECREF(res_str); |
232 |
|
ERM("Python call %s.%s returned '%s' - expected generator, int, or float value, got %s", |
233 |
|
module, funcname, |
234 |
|
PyString_AsString(res_str), |
235 |
|
retval.ptr()->ob_type->tp_name); |
236 |
|
status = INTERP_ERROR; |
237 |
|
} |
238 |
|
} else { |
239 |
4 |
logPy("call: O <%s> call returned None",funcname); |
240 |
4 |
frame->pystuff.impl->py_return_type = RET_NONE; |
241 |
|
} |
242 |
|
break; |
243 |
|
|
244 |
|
case PY_INTERNAL: |
245 |
|
case PY_PLUGIN_CALL: |
246 |
|
// a plain int (INTERP_OK, INTERP_ERROR, INTERP_EXECUTE_FINISH...) is expected |
247 |
|
// must have returned an int |
248 |
|
if ((retval.ptr() != Py_None) && |
249 |
|
(PyInt_Check(retval.ptr()))) { |
250 |
|
|
251 |
|
// FIXME check new return value convention |
252 |
|
status = frame->pystuff.impl->py_returned_int = bp::extract<int>(retval); |
253 |
|
frame->pystuff.impl->py_return_type = RET_INT; |
254 |
|
logPy("pycall(%s): PY_INTERNAL/PY_PLUGIN_CALL: return code=%d", funcname,status); |
255 |
|
} else { |
256 |
|
logPy("pycall(%s): PY_INTERNAL: expected an int return code", funcname); |
257 |
|
res_str = PyObject_Str(retval.ptr()); |
258 |
|
ERM("Python internal function '%s' expected tuple or int return value, got '%s' (%s)", |
259 |
|
funcname, |
260 |
|
PyString_AsString(res_str), |
261 |
|
retval.ptr()->ob_type->tp_name); |
262 |
|
Py_XDECREF(res_str); |
263 |
|
status = INTERP_ERROR; |
264 |
|
} |
265 |
|
break; |
266 |
|
case PY_EXECUTE: |
267 |
|
break; |
268 |
|
|
269 |
|
default: ; |
270 |
|
} |
271 |
|
} |
272 |
|
catch (bp::error_already_set) { |
273 |
|
if (PyErr_Occurred()) { |
274 |
|
msg = handle_pyerror(); |
275 |
|
} |
276 |
|
py_exception = true; |
277 |
|
bp::handle_exception(); |
278 |
|
PyErr_Clear(); |
279 |
|
} |
280 |
7 |
if (py_exception) { |
281 |
|
ERM("pycall: %s.%s:\n%s", module ? module:"", funcname, msg.c_str()); |
282 |
|
status = INTERP_ERROR; |
283 |
|
} |
284 |
7 |
return status; |
285 |
|
} |
286 |
|
|
287 |
|
// called by (py, ....) or ';py,...' comments |
288 |
19 |
int Interp::py_execute(const char *cmd, bool as_file) |
289 |
|
{ |
290 |
19 |
bp::object retval; |
291 |
|
|
292 |
19 |
logPy("py_execute(%s)",cmd); |
293 |
|
|
294 |
19 |
CHKS(!PYUSABLE, "py_execute(%s): Python plugin not initialized",cmd); |
295 |
|
|
296 |
19 |
python_plugin->run_string(cmd, retval, as_file); |
297 |
19 |
CHKS((python_plugin->plugin_status() == PLUGIN_EXCEPTION), |
298 |
|
"py_execute(%s)%s:\n%s", cmd, |
299 |
|
as_file ? " as file" : "", python_plugin->last_exception().c_str()); |
300 |
|
return INTERP_OK; |
301 |
|
} |
302 |
|
|
303 |
680 |
pycontext::pycontext() : impl(new pycontext_impl) {} |
304 |
|
pycontext::~pycontext() { delete impl; } |
305 |
|
pycontext::pycontext(const pycontext &other) |
306 |
|
: impl(new pycontext_impl(*other.impl)) {} |
307 |
|
pycontext &pycontext::operator=(const pycontext &other) { |
308 |
|
delete impl; |
309 |
|
impl = new pycontext_impl(*other.impl); |
310 |
|
return *this; |
311 |
207 |
} |