interpreter.cc 11 KB
Newer Older
1
2
3
4
// -*- mode: c++ -*-
// Copyright 2011, Florent Lamiraux, CNRS.

#include <iostream>
olivier stasse's avatar
olivier stasse committed
5
#include "dynamic-graph/debug.h"
6
#include "dynamic-graph/python/interpreter.hh"
7
#include "link-to-python.hh"
8

9
10
11
12
#include <boost/python/errors.hpp>
#include <boost/python/object.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/extract.hpp>
13
14
#include <boost/python/str.hpp>
#include <boost/python/import.hpp>
15

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
16
namespace py = boost::python;
17

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
18
std::ofstream dg_debugfile("/tmp/dynamic-graph-traces.txt", std::ios::trunc& std::ios::out);
19

20
21
// Python initialization commands
namespace dynamicgraph {
olivier stasse's avatar
olivier stasse committed
22
namespace python {
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static const std::string pythonPrefix[5] = {"import traceback\n",
                                            "def display(s): return str(s) if not s is None else None",
                                            "class StdoutCatcher:\n"
                                            "    def __init__(self):\n"
                                            "        self.data = ''\n"
                                            "    def write(self, stuff):\n"
                                            "        self.data = self.data + stuff\n"
                                            "    def fetch(self):\n"
                                            "        s = self.data[:]\n"
                                            "        self.data = ''\n"
                                            "        return s\n"
                                            "stdout_catcher = StdoutCatcher()\n"
                                            "import sys\n"
                                            "sys.stdout = stdout_catcher"};
37
}
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
38
}  // namespace dynamicgraph
39

40
namespace dynamicgraph {
olivier stasse's avatar
olivier stasse committed
41
namespace python {
42

43
44
45
46
// Parse a python exception and return the corresponding error message
// http://thejosephturner.com/blog/post/embedding-python-in-c-applications-with-boostpython-part-2/
std::string parse_python_exception();

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
47
bool HandleErr(std::string& err, PyObject* traceback_format_exception, PyObject* globals_, int PythonInputType) {
olivier stasse's avatar
olivier stasse committed
48
  dgDEBUGIN(15);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
49
50
  err = "";
  bool lres = false;
51
52
53
54
55
56

  if (PyErr_Occurred()) {
    PyObject *ptype, *pvalue, *ptraceback, *pyerr;
    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    if (ptraceback == NULL) {
      ptraceback = Py_None;
57
58
      // increase the Py_None count, to avoid a crash at the tuple destruction
      Py_INCREF(ptraceback);
59
60
61
62
63
64
65
    }
    PyObject* args = PyTuple_New(3);
    PyTuple_SET_ITEM(args, 0, ptype);
    PyTuple_SET_ITEM(args, 1, pvalue);
    PyTuple_SET_ITEM(args, 2, ptraceback);
    pyerr = PyObject_CallObject(traceback_format_exception, args);
    assert(PyList_Check(pyerr));
66
    Py_ssize_t size = PyList_GET_SIZE(pyerr);
67
    std::string stringRes;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
68
    for (Py_ssize_t i = 0; i < size; ++i) stringRes += std::string(PyString_AsString(PyList_GET_ITEM(pyerr, i)));
69
70
    Py_DecRef(pyerr);

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
71
    pyerr = PyString_FromString(stringRes.c_str());
72
    err = PyString_AsString(pyerr);
olivier stasse's avatar
olivier stasse committed
73
    dgDEBUG(15) << "err: " << err << std::endl;
74
    Py_DecRef(pyerr);
75

76
    // Here if there is a syntax error and
77
78
79
    // and the interpreter input is set to Py_eval_input,
    // it is maybe a statement instead of an expression.
    // Therefore we indicate to re-evaluate the command.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
80
    if (PyErr_GivenExceptionMatches(ptype, PyExc_SyntaxError) && (PythonInputType == Py_eval_input)) {
olivier stasse's avatar
olivier stasse committed
81
      dgDEBUG(15) << "Detected a syntax error " << std::endl;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
82
83
84
      lres = false;
    } else
      lres = true;
85

86
87
88
    Py_CLEAR(args);

    PyErr_Clear();
89
  } else {
olivier stasse's avatar
olivier stasse committed
90
    dgDEBUG(15) << "no object generated but no error occured." << std::endl;
91
  }
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
92
  PyObject* stdout_obj = PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
93
94
95
96
  std::string out("");

  out = PyString_AsString(stdout_obj);
  // Local display for the robot (in debug mode or for the logs)
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
97
98
99
100
101
  if (out.length() != 0) {
    dgDEBUG(15) << std::endl;
  } else {
    dgDEBUG(15) << "No exception." << std::endl;
  }
olivier stasse's avatar
olivier stasse committed
102
  dgDEBUGOUT(15);
103
  Py_DecRef(stdout_obj);
104
105
106
  return lres;
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
107
Interpreter::Interpreter() {
olivier stasse's avatar
olivier stasse committed
108
109
  // load python dynamic library
  // this is silly, but required to be able to import dl module.
110
#ifndef WIN32
olivier stasse's avatar
olivier stasse committed
111
  dlopen(libpython.c_str(), RTLD_LAZY | RTLD_GLOBAL);
112
#endif
olivier stasse's avatar
olivier stasse committed
113
  Py_Initialize();
Joseph Mirabel's avatar
Joseph Mirabel committed
114
  PyEval_InitThreads();
olivier stasse's avatar
olivier stasse committed
115
116
117
118
119
120
121
122
123
124
  mainmod_ = PyImport_AddModule("__main__");
  Py_INCREF(mainmod_);
  globals_ = PyModule_GetDict(mainmod_);
  assert(globals_);
  Py_INCREF(globals_);
  PyRun_SimpleString(pythonPrefix[0].c_str());
  PyRun_SimpleString(pythonPrefix[1].c_str());
  PyRun_SimpleString(pythonPrefix[2].c_str());
  PyRun_SimpleString(pythonPrefix[3].c_str());
  PyRun_SimpleString(pythonPrefix[4].c_str());
125
126
  PyRun_SimpleString("import linecache");

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
127
128
  traceback_format_exception_ =
      PyDict_GetItemString(PyModule_GetDict(PyImport_AddModule("traceback")), "format_exception");
olivier stasse's avatar
olivier stasse committed
129
130
  assert(PyCallable_Check(traceback_format_exception_));
  Py_INCREF(traceback_format_exception_);
Joseph Mirabel's avatar
Joseph Mirabel committed
131
132
133

  // Allow threads
  _pyState = PyEval_SaveThread();
olivier stasse's avatar
olivier stasse committed
134
}
135

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
136
Interpreter::~Interpreter() {
Joseph Mirabel's avatar
Joseph Mirabel committed
137
  PyEval_RestoreThread(_pyState);
138
139
140
141
142
143

  // Ideally, we should call Py_Finalize but this is not really supported by
  // Python.
  // Instead, we merelly remove variables.
  // Code was taken here: https://github.com/numpy/numpy/issues/8097#issuecomment-356683953
  {
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
144
145
146
    PyObject* poAttrList = PyObject_Dir(mainmod_);
    PyObject* poAttrIter = PyObject_GetIter(poAttrList);
    PyObject* poAttrName;
147

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
148
149
    while ((poAttrName = PyIter_Next(poAttrIter)) != NULL) {
      std::string oAttrName(PyString_AS_STRING(poAttrName));
150
151

      // Make sure we don't delete any private objects.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
152
153
      if (oAttrName.compare(0, 2, "__") != 0 || oAttrName.compare(oAttrName.size() - 2, 2, "__") != 0) {
        PyObject* poAttr = PyObject_GetAttr(mainmod_, poAttrName);
154
155

        // Make sure we don't delete any module objects.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
156
        if (poAttr && poAttr->ob_type != mainmod_->ob_type) PyObject_SetAttr(mainmod_, poAttrName, NULL);
157
158
159
160
161
162
163
164
165
166
167
168
169
170

        Py_DecRef(poAttr);
      }

      Py_DecRef(poAttrName);
    }

    Py_DecRef(poAttrIter);
    Py_DecRef(poAttrList);
  }

  Py_DECREF(mainmod_);
  Py_DECREF(globals_);
  Py_DECREF(traceback_format_exception_);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
171
  // Py_Finalize();
olivier stasse's avatar
olivier stasse committed
172
}
173

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
174
175
176
std::string Interpreter::python(const std::string& command) {
  std::string lerr(""), lout(""), lres("");
  python(command, lres, lout, lerr);
olivier stasse's avatar
olivier stasse committed
177
178
  return lres;
}
179

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
180
void Interpreter::python(const std::string& command, std::string& res, std::string& out, std::string& err) {
olivier stasse's avatar
olivier stasse committed
181
182
183
184
  res = "";
  out = "";
  err = "";

185
  // Check if the command is not a python comment or empty.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
186
  std::string::size_type iFirstNonWhite = command.find_first_not_of(" \t");
187
188
189
190
191
  // Empty command
  if (iFirstNonWhite == std::string::npos) return;
  // Command is a comment. Ignore it.
  if (command[iFirstNonWhite] == '#') return;

Joseph Mirabel's avatar
Joseph Mirabel committed
192
193
  PyEval_RestoreThread(_pyState);

olivier stasse's avatar
olivier stasse committed
194
  std::cout << command.c_str() << std::endl;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
195
  PyObject* result = PyRun_String(command.c_str(), Py_eval_input, globals_, globals_);
olivier stasse's avatar
olivier stasse committed
196
197
198
199
  // Check if the result is null.
  if (!result) {
    // Test if this is a syntax error (due to the evaluation of an expression)
    // else just output the problem.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
200
    if (!HandleErr(err, traceback_format_exception_, globals_, Py_eval_input)) {
olivier stasse's avatar
olivier stasse committed
201
202
203
204
      // If this is a statement, re-parse the command.
      result = PyRun_String(command.c_str(), Py_single_input, globals_, globals_);

      // If there is still an error build the appropriate err string.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
205
206
207
      if (result == NULL)
        HandleErr(err, traceback_format_exception_, globals_, Py_single_input);
      else
olivier stasse's avatar
olivier stasse committed
208
        // If there is no error, make sure that the previous error message is erased.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
209
210
211
        err = "";
    } else {
      dgDEBUG(15) << "Do not try a second time." << std::endl;
212
    }
olivier stasse's avatar
olivier stasse committed
213
  }
214

olivier stasse's avatar
olivier stasse committed
215
  PyObject* stdout_obj = 0;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
216
  stdout_obj = PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
olivier stasse's avatar
olivier stasse committed
217
218
  out = PyString_AsString(stdout_obj);
  // Local display for the robot (in debug mode or for the logs)
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
219
220
  if (out.size() != 0) std::cout << "Output:" << out << std::endl;
  if (err.size() != 0) std::cout << "Error:" << err << std::endl;
221
  PyObject* result2 = PyObject_Repr(result);
olivier stasse's avatar
olivier stasse committed
222
223
  // If python cannot build a string representation of result
  // then results is equal to NULL. This will trigger a SEGV
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
224
  if (result2 != NULL) {
olivier stasse's avatar
olivier stasse committed
225
    dgDEBUG(15) << "For command :" << command << std::endl;
226
    res = PyString_AsString(result2);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
227
228
    dgDEBUG(15) << "Result is: " << res << std::endl;
    dgDEBUG(15) << "Out is: " << out << std::endl;
olivier stasse's avatar
olivier stasse committed
229
    dgDEBUG(15) << "Err is :" << err << std::endl;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
230
231
  } else {
    dgDEBUG(15) << "Result is empty" << std::endl;
olivier stasse's avatar
olivier stasse committed
232
  }
233
234
235
  Py_DecRef(stdout_obj);
  Py_DecRef(result2);
  Py_DecRef(result);
Joseph Mirabel's avatar
Joseph Mirabel committed
236
237
238

  _pyState = PyEval_SaveThread();

olivier stasse's avatar
olivier stasse committed
239
240
  return;
}
241

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
242
PyObject* Interpreter::globals() { return globals_; }
243

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
244
void Interpreter::runPythonFile(std::string filename) {
245
246
247
248
  std::string err = "";
  runPythonFile(filename, err);
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
249
250
251
void Interpreter::runPythonFile(std::string filename, std::string& err) {
  FILE* pFile = fopen(filename.c_str(), "r");
  if (pFile == 0x0) {
252
253
254
255
    err = filename + " cannot be open";
    return;
  }

Joseph Mirabel's avatar
Joseph Mirabel committed
256
257
  PyEval_RestoreThread(_pyState);

258
  err = "";
olivier stasse's avatar
olivier stasse committed
259
  PyObject* pymainContext = globals_;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
260
  PyObject* run = PyRun_FileExFlags(pFile, filename.c_str(), Py_file_input, pymainContext, pymainContext, true, NULL);
261

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
262
  if (PyErr_Occurred()) {
263
    err = parse_python_exception();
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
264
265
    std::cerr << err << std::endl;
    ;
olivier stasse's avatar
olivier stasse committed
266
  }
Francois Keith's avatar
Francois Keith committed
267
  Py_DecRef(run);
Joseph Mirabel's avatar
Joseph Mirabel committed
268
269

  _pyState = PyEval_SaveThread();
olivier stasse's avatar
olivier stasse committed
270
}
271

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
272
273
void Interpreter::runMain(void) {
  const char* argv[] = {"dg-embedded-pysh"};
Joseph Mirabel's avatar
Joseph Mirabel committed
274
  PyEval_RestoreThread(_pyState);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
275
  Py_Main(1, const_cast<char**>(argv));
Joseph Mirabel's avatar
Joseph Mirabel committed
276
  _pyState = PyEval_SaveThread();
olivier stasse's avatar
olivier stasse committed
277
278
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
279
280
281
std::string Interpreter::processStream(std::istream& stream, std::ostream& os) {
  char line[10000];
  sprintf(line, "%s", "\n");
olivier stasse's avatar
olivier stasse committed
282
283
  std::string command;
  std::streamsize maxSize = 10000;
284
#if 0
olivier stasse's avatar
olivier stasse committed
285
286
287
288
  while (line != std::string("")) {
    stream.getline(line, maxSize, '\n');
    command += std::string(line) + std::string("\n");
  };
289
#else
olivier stasse's avatar
olivier stasse committed
290
291
292
  os << "dg> ";
  stream.getline(line, maxSize, ';');
  command += std::string(line);
293
#endif
olivier stasse's avatar
olivier stasse committed
294
295
  return command;
}
296

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
297
std::string parse_python_exception() {
298
299
300
301
  PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL;
  PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);
  std::string ret("Unfetchable Python error");

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
302
  if (type_ptr != NULL) {
303
304
305
    py::handle<> h_type(type_ptr);
    py::str type_pstr(h_type);
    py::extract<std::string> e_type_pstr(type_pstr);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
306
    if (e_type_pstr.check())
307
308
309
310
311
      ret = e_type_pstr();
    else
      ret = "Unknown exception type";
  }

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
312
  if (value_ptr != NULL) {
313
314
315
    py::handle<> h_val(value_ptr);
    py::str a(h_val);
    py::extract<std::string> returned(a);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
316
317
    if (returned.check())
      ret += ": " + returned();
318
319
320
321
    else
      ret += std::string(": Unparseable Python error: ");
  }

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
322
  if (traceback_ptr != NULL) {
323
324
325
326
327
328
    py::handle<> h_tb(traceback_ptr);
    py::object tb(py::import("traceback"));
    py::object fmt_tb(tb.attr("format_tb"));
    py::object tb_list(fmt_tb(h_tb));
    py::object tb_str(py::str("\n").join(tb_list));
    py::extract<std::string> returned(tb_str);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
329
    if (returned.check())
330
331
332
333
334
335
      ret += ": " + returned();
    else
      ret += std::string(": Unparseable Python traceback");
  }
  return ret;
}
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
336
337
}  // namespace python
}  // namespace dynamicgraph