/* This is a component of LinuxCNC * Copyright 2011, 2012 Michael Haberler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #define BOOST_PYTHON_MAX_ARITY 4 #include "python_plugin.hh" #include "interp_python.hh" #include #include #include namespace bp = boost::python; #include #include #include #include #include #include #include #include #include #include "rs274ngc.hh" #include "interp_return.hh" #include "interp_internal.hh" #include "rs274ngc_interp.hh" #include "units.h" extern PythonPlugin *python_plugin; #define PYCHK(bad, fmt, ...) \ do { \ if (bad) { \ logPy(fmt, ## __VA_ARGS__); \ ERM(fmt, ## __VA_ARGS__); \ goto error; \ } \ } while(0) // decode a Python exception into a string. std::string handle_pyerror() { PyObject *exc,*val,*tb; bp::object formatted_list, formatted; PyErr_Fetch(&exc,&val,&tb); PyErr_NormalizeException(&exc,&val,&tb); bp::handle<> hexc(bp::allow_null(exc)), hval(bp::allow_null(val)), htb(bp::allow_null(tb)); if(!hval) { return bp::extract(bp::str(hexc)); } else{ bp::object traceback(bp::import("traceback")); if (!tb) { bp::object format_exception_only(traceback.attr("format_exception_only")); formatted_list = format_exception_only(hexc,hval); } else { bp::object format_exception(traceback.attr("format_exception")); formatted_list = format_exception(hexc,hval,htb); } formatted = bp::str("\n").join(formatted_list); return bp::extract(formatted); } } int Interp::py_reload() { if (PYUSABLE) { CHKS((python_plugin->initialize() == PLUGIN_EXCEPTION), "py_reload:\n%s", python_plugin->last_exception().c_str()); } return INTERP_OK; } // determine whether [module.]funcname is callable bool Interp::is_pycallable(setup_pointer settings, const char *module, const char *funcname) { if (!PYUSABLE) return false; return python_plugin->is_callable(module,funcname); } // all parameters to/results from Python calls go through the callframe, which looks a bit awkward // the reason is not to expose boost.python through the interpreter public interface int Interp::pycall(setup_pointer settings, context_pointer frame, const char *module, const char *funcname, int calltype) { bp::object retval, function; std::string msg; bool py_exception = false; int status = INTERP_OK; PyObject *res_str; if (_setup.loggingLevel > 4) logPy("pycall(%s.%s) \n", module ? module : "", funcname); CHKS(!PYUSABLE, "pycall(%s): Python plugin not initialized",funcname); frame->pystuff.impl->py_return_type = 0; switch (calltype) { case PY_EXECUTE: // just run a string python_plugin->run_string(funcname, retval); CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, "run_string(%s):\n%s", funcname, python_plugin->last_exception().c_str()); break; // default: // switch (frame->entry_at) { //FIXTHIS terminally ugly case PY_FINISH_OWORDCALL: case PY_FINISH_PROLOG: case PY_FINISH_BODY: case PY_FINISH_EPILOG: logPy("pycall: call generator.next()" ); // check inputs here, since _read() may not be called read_inputs(&_setup); // handler continuation if a generator was used try { retval = frame->pystuff.impl->generator_next(); } catch (bp::error_already_set &) { if (PyErr_Occurred()) { // StopIteration is raised when the generator executes 'return' // instead of another 'yield INTERP_EXECUTE_FINISH // Technically this means a normal end of the handler and hence we // treat it as INTERP_OK indicating this handler is now done if (PyErr_ExceptionMatches(PyExc_StopIteration)) { frame->pystuff.impl->py_return_type = RET_STOPITERATION; bp::handle_exception(); PyErr_Clear(); logPy("pycall: call generator - StopIteration exception"); return INTERP_OK; } else { msg = handle_pyerror(); bp::handle_exception(); PyErr_Clear(); logPy("pycall: call generator - exception: %s",msg.c_str()); ERS("exception during generator call: %s", msg.c_str()); } } else Error("calling generator: duh"); } break; default: python_plugin->call(module,funcname, frame->pystuff.impl->tupleargs,frame->pystuff.impl->kwargs,retval); CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, "pycall(%s):\n%s", funcname, python_plugin->last_exception().c_str()); } try { status = INTERP_OK; switch (calltype) { case PY_OWORDCALL: case PY_PROLOG: case PY_BODY: case PY_EPILOG: case PY_FINISH_OWORDCALL: case PY_FINISH_PROLOG: case PY_FINISH_BODY: case PY_FINISH_EPILOG: // these may return values in several flavours: // - an int (INTERP_OK, INTERP_ERROR, INTERP_EXECUTE_FINISH...) // - a double (Python oword subroutine) // - a generator object, in case the handler contained a yield statement if (retval.ptr() != Py_None) { if (PyGen_Check(retval.ptr())) { // a generator was returned. This must have been the first time call to a handler // which contains a yield. Extract next() method. frame->pystuff.impl->generator_next = bp::getattr(retval, "__next__"); // and call it for the first time. // Expect execution up to first 'yield INTERP_EXECUTE_FINISH'. frame->pystuff.impl->py_returned_int = bp::extract(frame->pystuff.impl->generator_next()); frame->pystuff.impl->py_return_type = RET_YIELD; } else if (PyUnicode_Check(retval.ptr())) { // returning a string sets the interpreter error message and aborts char *msg = bp::extract(retval); ERM("%s", msg); frame->pystuff.impl->py_return_type = RET_ERRORMSG; status = INTERP_ERROR; } else if (PyLong_Check(retval.ptr())) { frame->pystuff.impl->py_returned_int = bp::extract(retval); frame->pystuff.impl->py_return_type = RET_INT; logPy("Python call %s.%s returned int: %d", module, funcname, frame->pystuff.impl->py_returned_int); } else if (PyFloat_Check(retval.ptr())) { frame->pystuff.impl->py_returned_double = bp::extract(retval); frame->pystuff.impl->py_return_type = RET_DOUBLE; logPy("Python call %s.%s returned float: %f", module, funcname, frame->pystuff.impl->py_returned_double); } else { // not a generator, int, or float - strange PyObject *res_str = PyObject_Str(retval.ptr()); Py_XDECREF(res_str); ERM("Python call %s.%s returned '%s' - expected generator, int, or float value, got %s", module, funcname, PyUnicode_AsUTF8(res_str), Py_TYPE(retval.ptr())->tp_name); status = INTERP_ERROR; } } else { logPy("call: O <%s> call returned None",funcname); frame->pystuff.impl->py_return_type = RET_NONE; } break; case PY_INTERNAL: case PY_PLUGIN_CALL: // a plain int (INTERP_OK, INTERP_ERROR, INTERP_EXECUTE_FINISH...) is expected // must have returned an int if ((retval.ptr() != Py_None) && (PyLong_Check(retval.ptr()))) { // FIXME check new return value convention status = frame->pystuff.impl->py_returned_int = bp::extract(retval); frame->pystuff.impl->py_return_type = RET_INT; logPy("pycall(%s): PY_INTERNAL/PY_PLUGIN_CALL: return code=%d", funcname,status); } else { logPy("pycall(%s): PY_INTERNAL: expected an int return code", funcname); res_str = PyObject_Str(retval.ptr()); ERM("Python internal function '%s' expected tuple or int return value, got '%s' (%s)", funcname, PyUnicode_AsUTF8(res_str), Py_TYPE(retval.ptr())->tp_name); Py_XDECREF(res_str); status = INTERP_ERROR; } break; case PY_EXECUTE: break; default: ; } } catch (bp::error_already_set &) { if (PyErr_Occurred()) { msg = handle_pyerror(); } py_exception = true; bp::handle_exception(); PyErr_Clear(); } if (py_exception) { ERM("pycall: %s.%s:\n%s", module ? module:"", funcname, msg.c_str()); status = INTERP_ERROR; } return status; } // called by (py, ....) or ';py,...' comments int Interp::py_execute(const char *cmd, bool as_file) { bp::object retval; logPy("py_execute(%s)",cmd); CHKS(!PYUSABLE, "py_execute(%s): Python plugin not initialized",cmd); python_plugin->run_string(cmd, retval, as_file); CHKS((python_plugin->plugin_status() == PLUGIN_EXCEPTION), "py_execute(%s)%s:\n%s", cmd, as_file ? " as file" : "", python_plugin->last_exception().c_str()); return INTERP_OK; } pycontext::pycontext() : impl(new pycontext_impl) {} pycontext::~pycontext() { delete impl; } pycontext::pycontext(const pycontext &other) : impl(new pycontext_impl(*other.impl)) {} pycontext &pycontext::operator=(const pycontext &other) { delete impl; impl = new pycontext_impl(*other.impl); return *this; }