GCC Code Coverage Report
Directory: emc/rs274ngc/ Exec Total Coverage
File: emc/rs274ngc/interp_python.cc Lines: 45 107 42.1 %
Date: 2016-10-27 Branches: 42 273 15.4 %

Line Exec Source
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
}