GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/interpreter.cc Lines: 90 112 80.4 %
Date: 2022-09-30 08:22:45 Branches: 32 68 47.1 %

Line Branch Exec Source
1
// -*- mode: c++ -*-
2
// Copyright 2011, Florent Lamiraux, CNRS.
3
4
#ifdef WIN32
5
#include <Windows.h>
6
#else
7
#include <dlfcn.h>
8
#endif
9
10
#include <iostream>
11
12
#include "dynamic-graph/debug.h"
13
#include "dynamic-graph/python/interpreter.hh"
14
15
std::ofstream dg_debugfile("/tmp/dynamic-graph-traces.txt",
16
                           std::ios::trunc& std::ios::out);
17
18
// Python initialization commands
19
namespace dynamicgraph {
20
namespace python {
21
static const std::string pythonPrefix[8] = {
22
    "from __future__ import print_function\n",
23
    "import traceback\n",
24
    "class StdoutCatcher:\n"
25
    "    def __init__(self):\n"
26
    "        self.data = ''\n"
27
    "    def write(self, stuff):\n"
28
    "        self.data = self.data + stuff\n"
29
    "    def fetch(self):\n"
30
    "        s = self.data[:]\n"
31
    "        self.data = ''\n"
32
    "        return s\n",
33
    "stdout_catcher = StdoutCatcher()\n",
34
    "stderr_catcher = StdoutCatcher()\n",
35
    "import sys\n",
36
    "sys.stdout = stdout_catcher",
37
    "sys.stderr = stderr_catcher"};
38
39
2052
bool HandleErr(std::string& err, PyObject* globals_, int PythonInputType) {
40
  dgDEBUGIN(15);
41
2052
  err = "";
42
2052
  bool lres = false;
43
44
2052
  if (PyErr_Occurred() != NULL) {
45
2052
    bool is_syntax_error = PyErr_ExceptionMatches(PyExc_SyntaxError);
46
2052
    PyErr_Print();
47
2052
    PyObject* stderr_obj = PyRun_String("stderr_catcher.fetch()", Py_eval_input,
48
                                        globals_, globals_);
49
2052
    err = obj_to_str(stderr_obj);
50
2052
    Py_DECREF(stderr_obj);
51
52
    // Here if there is a syntax error and
53
    // and the interpreter input is set to Py_eval_input,
54
    // it is maybe a statement instead of an expression.
55
    // Therefore we indicate to re-evaluate the command.
56

2052
    if (is_syntax_error && PythonInputType == Py_eval_input) {
57
      dgDEBUG(15) << "Detected a syntax error " << std::endl;
58
1
      lres = false;
59
    } else
60
2051
      lres = true;
61
62
2052
    PyErr_Clear();
63
  } else {
64
    dgDEBUG(15) << "no object generated but no error occured." << std::endl;
65
  }
66
2052
  return lres;
67
}
68
69
7
Interpreter::Interpreter() {
70
  // load python dynamic library
71
  // this is silly, but required to be able to import dl module.
72
#ifndef WIN32
73
7
  dlopen(PYTHON_LIBRARY, RTLD_LAZY | RTLD_GLOBAL);
74
#endif
75
7
  Py_Initialize();
76
#if PY_MAJOR_VERSION < 3 || PY_MINOR_VERSION < 7
77
  PyEval_InitThreads();
78
#endif
79
7
  mainmod_ = PyImport_AddModule("__main__");
80
7
  Py_INCREF(mainmod_);
81
7
  globals_ = PyModule_GetDict(mainmod_);
82
7
  assert(globals_);
83
7
  Py_INCREF(globals_);
84
7
  PyRun_SimpleString(pythonPrefix[0].c_str());
85
7
  PyRun_SimpleString(pythonPrefix[1].c_str());
86
7
  PyRun_SimpleString(pythonPrefix[2].c_str());
87
7
  PyRun_SimpleString(pythonPrefix[3].c_str());
88
7
  PyRun_SimpleString(pythonPrefix[4].c_str());
89
7
  PyRun_SimpleString(pythonPrefix[5].c_str());
90
7
  PyRun_SimpleString(pythonPrefix[6].c_str());
91
7
  PyRun_SimpleString(pythonPrefix[7].c_str());
92
7
  PyRun_SimpleString("import linecache");
93
94
  // Allow threads
95
7
  _pyState = PyEval_SaveThread();
96
7
}
97
98
14
Interpreter::~Interpreter() {
99
7
  PyEval_RestoreThread(_pyState);
100
101
  // Ideally, we should call Py_Finalize but this is not really supported by
102
  // Python.
103
  // Instead, we merelly remove variables.
104
  // Code was taken here:
105
  // https://github.com/numpy/numpy/issues/8097#issuecomment-356683953
106
  {
107
7
    PyObject* poAttrList = PyObject_Dir(mainmod_);
108
7
    PyObject* poAttrIter = PyObject_GetIter(poAttrList);
109
    PyObject* poAttrName;
110
111
123
    while ((poAttrName = PyIter_Next(poAttrIter)) != NULL) {
112
232
      std::string oAttrName(obj_to_str(poAttrName));
113
114
      // Make sure we don't delete any private objects.
115

165
      if (oAttrName.compare(0, 2, "__") != 0 ||
116
49
          oAttrName.compare(oAttrName.size() - 2, 2, "__") != 0) {
117
67
        PyObject* poAttr = PyObject_GetAttr(mainmod_, poAttrName);
118
119
        // Make sure we don't delete any module objects.
120

67
        if (poAttr && poAttr->ob_type != mainmod_->ob_type)
121
33
          PyObject_SetAttr(mainmod_, poAttrName, NULL);
122
123
67
        Py_DECREF(poAttr);
124
      }
125
126
116
      Py_DECREF(poAttrName);
127
    }
128
129
7
    Py_DECREF(poAttrIter);
130
7
    Py_DECREF(poAttrList);
131
  }
132
133
7
  Py_DECREF(mainmod_);
134
7
  Py_DECREF(globals_);
135
  // Py_Finalize();
136
7
}
137
138
std::string Interpreter::python(const std::string& command) {
139
  std::string lerr(""), lout(""), lres("");
140
  python(command, lres, lout, lerr);
141
  return lres;
142
}
143
144
2
void Interpreter::python(const std::string& command, std::string& res,
145
                         std::string& out, std::string& err) {
146
2
  res = "";
147
2
  out = "";
148
2
  err = "";
149
150
  // Check if the command is not a python comment or empty.
151
2
  std::string::size_type iFirstNonWhite = command.find_first_not_of(" \t");
152
  // Empty command
153
2
  if (iFirstNonWhite == std::string::npos) return;
154
  // Command is a comment. Ignore it.
155
2
  if (command[iFirstNonWhite] == '#') return;
156
157
2
  PyEval_RestoreThread(_pyState);
158
159
2
  std::cout << command.c_str() << std::endl;
160
  PyObject* result =
161
2
      PyRun_String(command.c_str(), Py_eval_input, globals_, globals_);
162
  // Check if the result is null.
163
2
  if (result == NULL) {
164
    // Test if this is a syntax error (due to the evaluation of an expression)
165
    // else just output the problem.
166
1
    if (!HandleErr(err, globals_, Py_eval_input)) {
167
      // If this is a statement, re-parse the command.
168
      result =
169
1
          PyRun_String(command.c_str(), Py_single_input, globals_, globals_);
170
171
      // If there is still an error build the appropriate err string.
172
1
      if (result == NULL) HandleErr(err, globals_, Py_single_input);
173
      // If there is no error, make sure that the previous error message is
174
      // erased.
175
      else
176
        err = "";
177
    } else
178
      dgDEBUG(15) << "Do not try a second time." << std::endl;
179
  }
180
181
2
  PyObject* stdout_obj = 0;
182
  stdout_obj =
183
2
      PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
184
2
  out = obj_to_str(stdout_obj);
185
2
  Py_DECREF(stdout_obj);
186
  // Local display for the robot (in debug mode or for the logs)
187
2
  if (out.size() != 0) std::cout << "Output:" << out << std::endl;
188
2
  if (err.size() != 0) std::cout << "Error:" << err << std::endl;
189
  // If python cannot build a string representation of result
190
  // then results is equal to NULL. This will trigger a SEGV
191
  dgDEBUG(15) << "For command: " << command << std::endl;
192
2
  if (result != NULL) {
193
1
    res = obj_to_str(result);
194
    dgDEBUG(15) << "Result is: " << res << std::endl;
195
1
    Py_DECREF(result);
196
  } else {
197
    dgDEBUG(15) << "Result is: empty" << std::endl;
198
  }
199
  dgDEBUG(15) << "Out is: " << out << std::endl;
200
  dgDEBUG(15) << "Err is :" << err << std::endl;
201
202
2
  _pyState = PyEval_SaveThread();
203
204
2
  return;
205
}
206
207
PyObject* Interpreter::globals() { return globals_; }
208
209
void Interpreter::runPythonFile(std::string filename) {
210
  std::string err = "";
211
  runPythonFile(filename, err);
212
}
213
214
4102
void Interpreter::runPythonFile(std::string filename, std::string& err) {
215
4102
  FILE* pFile = fopen(filename.c_str(), "r");
216
4102
  if (pFile == 0x0) {
217
1025
    err = filename + " cannot be open";
218
1025
    return;
219
  }
220
221
3077
  PyEval_RestoreThread(_pyState);
222
223
3077
  err = "";
224
  PyObject* run =
225
3077
      PyRun_File(pFile, filename.c_str(), Py_file_input, globals_, globals_);
226
3077
  if (run == NULL) {
227
2050
    HandleErr(err, globals_, Py_file_input);
228
2050
    std::cerr << err << std::endl;
229
  }
230
3077
  Py_DecRef(run);
231
232
3077
  _pyState = PyEval_SaveThread();
233
3077
  fclose(pFile);
234
}
235
236
void Interpreter::runMain(void) {
237
  PyEval_RestoreThread(_pyState);
238
#if PY_MAJOR_VERSION >= 3
239
  const Py_UNICODE* argv[] = {L"dg-embedded-pysh"};
240
  Py_Main(1, const_cast<Py_UNICODE**>(argv));
241
#else
242
  const char* argv[] = {"dg-embedded-pysh"};
243
  Py_Main(1, const_cast<char**>(argv));
244
#endif
245
  _pyState = PyEval_SaveThread();
246
}
247
248
std::string Interpreter::processStream(std::istream& stream, std::ostream& os) {
249
  char line[10000];
250
  sprintf(line, "%s", "\n");
251
  std::string command;
252
  std::streamsize maxSize = 10000;
253
#if 0
254
  while (line != std::string("")) {
255
    stream.getline(line, maxSize, '\n');
256
    command += std::string(line) + std::string("\n");
257
  };
258
#else
259
  os << "dg> ";
260
  stream.getline(line, maxSize, ';');
261
  command += std::string(line);
262
#endif
263
  return command;
264
}
265
266
}  // namespace python
267
}  // namespace dynamicgraph