interpreter.cc 8.57 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 "dynamic-graph/python/link-to-python.hh"
8

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

11
12
// Python initialization commands
namespace dynamicgraph {
olivier stasse's avatar
olivier stasse committed
13
namespace python {
Guilhem Saurel's avatar
Guilhem Saurel committed
14
static const std::string pythonPrefix[7] = {"import traceback\n",
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
15
16
17
18
19
20
21
22
                                            "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"
Guilhem Saurel's avatar
Guilhem Saurel committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
                                            "        return s\n",
                                            "stdout_catcher = StdoutCatcher()\n",
                                            "stderr_catcher = StdoutCatcher()\n",
                                            "import sys\n",
                                            "sys.stdout = stdout_catcher",
                                            "sys.stderr = stderr_catcher"};

// Get any PyObject and get its str() representation as an std::string
std::string obj_to_str(PyObject* o) {
  std::string ret;
  PyObject* os;
#if PY_MAJOR_VERSION >= 3
  os = PyObject_Str(o);
  assert(os != NULL);
  assert(PyUnicode_Check(os));
  ret = PyUnicode_AsUTF8(os);
#else
  os = PyObject_Unicode(o);
  assert(os != NULL);
  assert(PyUnicode_Check(os));
  PyObject* oss = PyUnicode_AsUTF8String(os);
  assert(oss != NULL);
  ret = PyString_AsString(oss);
  Py_DECREF(oss);
#endif
  Py_DECREF(os);
  return ret;
50
51
}

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

Guilhem Saurel's avatar
Guilhem Saurel committed
57
58
59
60
61
62
  if (PyErr_Occurred() != NULL) {
    bool is_syntax_error = PyErr_ExceptionMatches(PyExc_SyntaxError);
    PyErr_Print();
    PyObject* stderr_obj = PyRun_String("stderr_catcher.fetch()", Py_eval_input, globals_, globals_);
    err = obj_to_str(stderr_obj);
    Py_DECREF(stderr_obj);
63

64
    // Here if there is a syntax error and
65
66
67
    // 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
Guilhem Saurel committed
68
    if (is_syntax_error && PythonInputType == Py_eval_input) {
olivier stasse's avatar
olivier stasse committed
69
      dgDEBUG(15) << "Detected a syntax error " << std::endl;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
70
71
72
      lres = false;
    } else
      lres = true;
73

74
    PyErr_Clear();
75
  } else {
olivier stasse's avatar
olivier stasse committed
76
    dgDEBUG(15) << "no object generated but no error occured." << std::endl;
77
78
79
80
  }
  return lres;
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
81
Interpreter::Interpreter() {
olivier stasse's avatar
olivier stasse committed
82
83
  // load python dynamic library
  // this is silly, but required to be able to import dl module.
84
#ifndef WIN32
olivier stasse's avatar
olivier stasse committed
85
  dlopen(libpython.c_str(), RTLD_LAZY | RTLD_GLOBAL);
86
#endif
olivier stasse's avatar
olivier stasse committed
87
  Py_Initialize();
Joseph Mirabel's avatar
Joseph Mirabel committed
88
  PyEval_InitThreads();
olivier stasse's avatar
olivier stasse committed
89
90
91
92
93
94
95
96
97
98
  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());
Guilhem Saurel's avatar
Guilhem Saurel committed
99
100
  PyRun_SimpleString(pythonPrefix[5].c_str());
  PyRun_SimpleString(pythonPrefix[6].c_str());
101
102
  PyRun_SimpleString("import linecache");

Joseph Mirabel's avatar
Joseph Mirabel committed
103
104
  // Allow threads
  _pyState = PyEval_SaveThread();
olivier stasse's avatar
olivier stasse committed
105
}
106

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
107
Interpreter::~Interpreter() {
Joseph Mirabel's avatar
Joseph Mirabel committed
108
  PyEval_RestoreThread(_pyState);
109
110
111
112
113
114

  // 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
115
116
117
    PyObject* poAttrList = PyObject_Dir(mainmod_);
    PyObject* poAttrIter = PyObject_GetIter(poAttrList);
    PyObject* poAttrName;
118

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
119
    while ((poAttrName = PyIter_Next(poAttrIter)) != NULL) {
Guilhem Saurel's avatar
Guilhem Saurel committed
120
      std::string oAttrName(obj_to_str(poAttrName));
121
122

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

        // Make sure we don't delete any module objects.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
127
        if (poAttr && poAttr->ob_type != mainmod_->ob_type) PyObject_SetAttr(mainmod_, poAttrName, NULL);
128

Guilhem Saurel's avatar
Guilhem Saurel committed
129
        Py_DECREF(poAttr);
130
131
      }

Guilhem Saurel's avatar
Guilhem Saurel committed
132
      Py_DECREF(poAttrName);
133
134
    }

Guilhem Saurel's avatar
Guilhem Saurel committed
135
136
    Py_DECREF(poAttrIter);
    Py_DECREF(poAttrList);
137
138
139
140
  }

  Py_DECREF(mainmod_);
  Py_DECREF(globals_);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
141
  // Py_Finalize();
olivier stasse's avatar
olivier stasse committed
142
}
143

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
144
145
146
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
147
148
  return lres;
}
149

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
150
void Interpreter::python(const std::string& command, std::string& res, std::string& out, std::string& err) {
olivier stasse's avatar
olivier stasse committed
151
152
153
154
  res = "";
  out = "";
  err = "";

155
  // Check if the command is not a python comment or empty.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
156
  std::string::size_type iFirstNonWhite = command.find_first_not_of(" \t");
157
158
159
160
161
  // 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
162
163
  PyEval_RestoreThread(_pyState);

olivier stasse's avatar
olivier stasse committed
164
  std::cout << command.c_str() << std::endl;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
165
  PyObject* result = PyRun_String(command.c_str(), Py_eval_input, globals_, globals_);
olivier stasse's avatar
olivier stasse committed
166
  // Check if the result is null.
Guilhem Saurel's avatar
Guilhem Saurel committed
167
  if (result == NULL) {
olivier stasse's avatar
olivier stasse committed
168
169
    // Test if this is a syntax error (due to the evaluation of an expression)
    // else just output the problem.
Guilhem Saurel's avatar
Guilhem Saurel committed
170
    if (!HandleErr(err, globals_, Py_eval_input)) {
olivier stasse's avatar
olivier stasse committed
171
172
173
174
      // 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
Guilhem Saurel committed
175
176
      if (result == NULL) HandleErr(err, globals_, Py_single_input);
      // If there is no error, make sure that the previous error message is erased.
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
177
178
      else
        err = "";
Guilhem Saurel's avatar
Guilhem Saurel committed
179
    } else
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
180
      dgDEBUG(15) << "Do not try a second time." << std::endl;
olivier stasse's avatar
olivier stasse committed
181
  }
182

olivier stasse's avatar
olivier stasse committed
183
  PyObject* stdout_obj = 0;
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
184
  stdout_obj = PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
Guilhem Saurel's avatar
Guilhem Saurel committed
185
186
  out = obj_to_str(stdout_obj);
  Py_DECREF(stdout_obj);
olivier stasse's avatar
olivier stasse committed
187
  // Local display for the robot (in debug mode or for the logs)
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
188
189
  if (out.size() != 0) std::cout << "Output:" << out << std::endl;
  if (err.size() != 0) std::cout << "Error:" << err << std::endl;
olivier stasse's avatar
olivier stasse committed
190
191
  // If python cannot build a string representation of result
  // then results is equal to NULL. This will trigger a SEGV
Guilhem Saurel's avatar
Guilhem Saurel committed
192
193
194
  dgDEBUG(15) << "For command: " << command << std::endl;
  if (result != NULL) {
    res = obj_to_str(result);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
195
    dgDEBUG(15) << "Result is: " << res << std::endl;
Guilhem Saurel's avatar
Guilhem Saurel committed
196
    Py_DECREF(result);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
197
  } else {
Guilhem Saurel's avatar
Guilhem Saurel committed
198
    dgDEBUG(15) << "Result is: empty" << std::endl;
olivier stasse's avatar
olivier stasse committed
199
  }
Guilhem Saurel's avatar
Guilhem Saurel committed
200
201
  dgDEBUG(15) << "Out is: " << out << std::endl;
  dgDEBUG(15) << "Err is :" << err << std::endl;
Joseph Mirabel's avatar
Joseph Mirabel committed
202
203
204

  _pyState = PyEval_SaveThread();

olivier stasse's avatar
olivier stasse committed
205
206
  return;
}
207

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

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
210
void Interpreter::runPythonFile(std::string filename) {
211
212
213
214
  std::string err = "";
  runPythonFile(filename, err);
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
215
216
217
void Interpreter::runPythonFile(std::string filename, std::string& err) {
  FILE* pFile = fopen(filename.c_str(), "r");
  if (pFile == 0x0) {
218
219
220
221
    err = filename + " cannot be open";
    return;
  }

Joseph Mirabel's avatar
Joseph Mirabel committed
222
223
  PyEval_RestoreThread(_pyState);

224
  err = "";
Guilhem Saurel's avatar
Guilhem Saurel committed
225
226
227
  PyObject* run = PyRun_File(pFile, filename.c_str(), Py_file_input, globals_, globals_);
  if (run == NULL) {
    HandleErr(err, globals_, Py_file_input);
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
228
    std::cerr << err << std::endl;
olivier stasse's avatar
olivier stasse committed
229
  }
Francois Keith's avatar
Francois Keith committed
230
  Py_DecRef(run);
Joseph Mirabel's avatar
Joseph Mirabel committed
231
232

  _pyState = PyEval_SaveThread();
Guilhem Saurel's avatar
Guilhem Saurel committed
233
  fclose(pFile);
olivier stasse's avatar
olivier stasse committed
234
}
235

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
236
void Interpreter::runMain(void) {
Joseph Mirabel's avatar
Joseph Mirabel committed
237
  PyEval_RestoreThread(_pyState);
Guilhem Saurel's avatar
Guilhem Saurel committed
238
#if PY_MAJOR_VERSION >= 3
Guilhem Saurel's avatar
Guilhem Saurel committed
239
240
  const Py_UNICODE* argv[] = {L"dg-embedded-pysh"};
  Py_Main(1, const_cast<Py_UNICODE**>(argv));
Guilhem Saurel's avatar
Guilhem Saurel committed
241
242
#else
  const char* argv[] = {"dg-embedded-pysh"};
Guilhem Saurel's avatar
format    
Guilhem Saurel committed
243
  Py_Main(1, const_cast<char**>(argv));
Guilhem Saurel's avatar
Guilhem Saurel committed
244
#endif
Joseph Mirabel's avatar
Joseph Mirabel committed
245
  _pyState = PyEval_SaveThread();
olivier stasse's avatar
olivier stasse committed
246
247
}

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
248
249
250
std::string Interpreter::processStream(std::istream& stream, std::ostream& os) {
  char line[10000];
  sprintf(line, "%s", "\n");
olivier stasse's avatar
olivier stasse committed
251
252
  std::string command;
  std::streamsize maxSize = 10000;
253
#if 0
olivier stasse's avatar
olivier stasse committed
254
255
256
257
  while (line != std::string("")) {
    stream.getline(line, maxSize, '\n');
    command += std::string(line) + std::string("\n");
  };
258
#else
olivier stasse's avatar
olivier stasse committed
259
260
261
  os << "dg> ";
  stream.getline(line, maxSize, ';');
  command += std::string(line);
262
#endif
olivier stasse's avatar
olivier stasse committed
263
264
  return command;
}
265

Guilhem Saurel's avatar
format    
Guilhem Saurel committed
266
267
}  // namespace python
}  // namespace dynamicgraph