Salome HOME
Issue #3237: Allow usage of accented characters in ObjectBrowser
[modules/shaper.git] / src / InitializationPlugin / InitializationPlugin_PyInterp.cpp
1 // Copyright (C) 2014-2020  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include <InitializationPlugin_PyInterp.h>
21
22 #include <Locale_Convert.h>
23
24 #include <string>
25 #include <stdexcept>
26 #include <clocale>
27
28 InitializationPlugin_PyInterp::InitializationPlugin_PyInterp()
29 : PyInterp_Interp()
30 {
31 }
32
33 InitializationPlugin_PyInterp::~InitializationPlugin_PyInterp()
34 {
35 }
36
37 const char* aSearchCode =
38   "import ast\n"
39   "class FindName(ast.NodeVisitor):\n"
40   "    def __init__(self, name):\n"
41   "        self.name = name\n"
42   "    def visit_Name(self, node):\n"
43   "        if node.id == self.name:\n"
44   "            positions.append((node.lineno, node.col_offset))\n"
45   "FindName(name).visit(ast.parse(expression))";
46
47 // make the expression be correct for the python interpreter even for the
48 // beta=alfa*2 expressions
49 static std::wstring adjustExpression(const std::wstring& theExpression) {
50   std::wstring anExpression = theExpression;
51   if (!anExpression.empty() && anExpression.back() == L'=') {
52     anExpression = anExpression.substr(0, anExpression.length() - 1);
53   }
54   return anExpression;
55 }
56
57 std::list<std::pair<int, int> >
58 InitializationPlugin_PyInterp::positions(const std::wstring& theExpression,
59                                          const std::wstring& theName)
60 {
61   PyLockWrapper lck; // Acquire GIL until the end of the method
62
63   std::list<std::pair<int, int> > aResult;
64
65   // prepare a context
66   PyObject* aContext = PyDict_New();
67   PyDict_SetItemString(aContext, "__builtins__", PyEval_GetBuiltins());
68
69   std::wstring anExpression = adjustExpression(theExpression);
70   // extend aContext with variables
71   PyDict_SetItemString(aContext, "expression",
72       PyUnicode_FromWideChar(anExpression.c_str(), anExpression.size()));
73   PyDict_SetItemString(aContext, "name", PyUnicode_FromWideChar(theName.c_str(), theName.size()));
74   PyDict_SetItemString(aContext, "positions", Py_BuildValue("[]"));
75
76   // run the search code
77   PyObject* aExecResult = PyRun_String(aSearchCode, Py_file_input, aContext, aContext);
78   Py_XDECREF(aExecResult);
79
80   // receive results from context
81   PyObject* aPositions = PyDict_GetItemString(aContext, "positions");
82   for (int anIndex = 0; anIndex < PyList_Size(aPositions); ++anIndex) {
83     PyObject* aPosition = PyList_GetItem(aPositions, anIndex);
84     PyObject* aLineNo = PyTuple_GetItem(aPosition, 0);
85     PyObject* aColOffset = PyTuple_GetItem(aPosition, 1);
86
87     aResult.push_back(
88         std::pair<int, int>((int)PyLong_AsLong(aLineNo),
89                             (int)PyLong_AsLong(aColOffset)));
90   }
91
92   // TODO(spo): after this refCount of the variable is not 0. Is there memory leak?
93   Py_DECREF(aContext);
94
95   return aResult;
96 }
97
98
99 std::list<std::wstring> InitializationPlugin_PyInterp::compile(const std::wstring& theExpression)
100 {
101   PyLockWrapper lck; // Acquire GIL until the end of the method
102   std::list<std::wstring> aResult;
103   PyObject *aCodeopModule = PyImport_AddModule("codeop");
104   if(!aCodeopModule) { // Fatal error. No way to go on.
105     PyErr_Print();
106     return aResult;
107   }
108   // support "variable_name=" expression as "variable_name"
109   std::wstring anExpression = adjustExpression(theExpression);
110
111   PyObject *aCodePyObj =
112     PyObject_CallMethod(aCodeopModule, (char*)"compile_command", (char*)"(s)",
113                         Locale::Convert::toString(anExpression).c_str());
114
115   if(!aCodePyObj || aCodePyObj == Py_None || !PyCode_Check(aCodePyObj)) {
116     Py_XDECREF(aCodePyObj);
117     return aResult;
118   }
119
120   PyCodeObject* aCodeObj = (PyCodeObject*) aCodePyObj;
121   std::string aCodeName(PyBytes_AsString(aCodeObj->co_code));
122   // co_names should be tuple, but can be changed in modern versions of python (>2.7.3)
123   if(!PyTuple_Check(aCodeObj->co_names)) {
124     return aResult;
125   }
126
127   size_t params_size = PyTuple_Size(aCodeObj->co_names);
128   if (params_size > 0) {
129     for (size_t i = 0; i < params_size; i++) {
130       PyObject* aParamObj = PyTuple_GetItem(aCodeObj->co_names, i);
131       PyObject* aParamObjStr = PyObject_Str(aParamObj);
132       Py_ssize_t aSize;
133       std::wstring aParamName(PyUnicode_AsWideCharString(aParamObjStr, &aSize));
134       aResult.push_back(aParamName);
135       Py_XDECREF(aParamObjStr);
136     }
137   }
138   Py_XDECREF(aCodeObj);
139   return aResult;
140 }
141
142 void InitializationPlugin_PyInterp::extendLocalContext(const std::list<std::wstring>& theParameters)
143 {
144   PyLockWrapper lck; // Acquire GIL until the end of the method
145   if (theParameters.empty())
146     return;
147   std::list<std::wstring>::const_iterator it = theParameters.begin();
148   for ( ; it != theParameters.cend(); it++) {
149     std::string aParamValue = Locale::Convert::toString(*it);
150     simpleRun(aParamValue.c_str(), false);
151   }
152 }
153
154 void InitializationPlugin_PyInterp::clearLocalContext()
155 {
156   PyLockWrapper lck;
157   PyDict_Clear(_local_context);
158 }
159
160 double InitializationPlugin_PyInterp::evaluate(const std::wstring& theExpression,
161                                                std::string& theError)
162 {
163   // support "variable_name=" expression as "variable_name"
164   std::wstring anExpression = adjustExpression(theExpression);
165
166   PyLockWrapper lck; // Acquire GIL until the end of the method
167   PyCompilerFlags aFlags = {CO_FUTURE_DIVISION};
168   aFlags.cf_flags = CO_FUTURE_DIVISION;
169   PyCodeObject* anExprCode = (PyCodeObject *) Py_CompileStringFlags(
170                                 Locale::Convert::toString(anExpression).c_str(),
171                                 "<string>", Py_eval_input, &aFlags);
172   if(!anExprCode) {
173     theError = errorMessage();
174     Py_XDECREF(anExprCode);
175     return 0.;
176   }
177
178   PyObject* anEvalResult = PyEval_EvalCode((PyObject *)anExprCode, _global_context, _local_context);
179   if(!anEvalResult) {
180     theError = errorMessage();
181     Py_XDECREF(anExprCode);
182     Py_XDECREF(anEvalResult);
183     return 0.;
184   }
185
186   PyObject* anEvalStrObj = PyObject_Str(anEvalResult);
187   std::string anEvalStr(PyUnicode_AsUTF8(anEvalStrObj));
188   Py_XDECREF(anExprCode);
189   Py_XDECREF(anEvalResult);
190   Py_XDECREF(anEvalStrObj);
191   double result = 0.;
192   try {
193     // set locale due to the #2485
194     std::string aCurLocale = std::setlocale(LC_NUMERIC, 0);
195     std::setlocale(LC_NUMERIC, "C");
196     result = std::stod(anEvalStr);
197     std::setlocale(LC_NUMERIC, aCurLocale.c_str());
198   }
199   catch (const std::invalid_argument&) {
200     theError = "Unable to eval " + anEvalStr;
201   }
202
203   return result;
204 }
205
206 std::string InitializationPlugin_PyInterp::errorMessage()
207 {
208   std::string aPyError;
209   if (PyErr_Occurred()) {
210     PyObject *pstr, *ptype, *pvalue, *ptraceback;
211     PyErr_Fetch(&ptype, &pvalue, &ptraceback);
212     PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
213     pstr = PyObject_Str(pvalue);
214     aPyError = std::string(PyUnicode_AsUTF8(pstr));
215     Py_XDECREF(pstr);
216     Py_XDECREF(ptype);
217     Py_XDECREF(pvalue);
218     Py_XDECREF(ptraceback);
219   }
220   return aPyError;
221 }
222
223 bool InitializationPlugin_PyInterp::initContext()
224 {
225   PyObject *m = PyImport_AddModule("__main__");  // interpreter main module (module context)
226   if(!m){
227     PyErr_Print();
228     return false;
229   }
230   _global_context = PyModule_GetDict(m);          // get interpreter global variable context
231   Py_INCREF(_global_context);
232   _local_context = PyDict_New();
233   Py_INCREF(_local_context);
234
235   // to avoid "help()" hang in the python console
236   const static char* aHelpTxt = "def help(): print(\"Available modules:\\n"
237     "  salome.shaper.model : higher level access to features and data model\\n"
238     "  BuildAPI            : Build plugin features allowing to build shapes\\n"
239     "  ConfigAPI           : configuration management: preferences and XML properties\\n"
240     "  ConstructionAPI     : Construction plugin for auxiliary features creation\\n"
241     "  EventsAPI           : application events receiving and emitting manager\\n"
242     "  ExchangeAPI         : Exchange plugin with import/export features\\n"
243     "  FeaturesAPI         : Features plugin with general 3D features\\n"
244     "  GeomAlgoAPI         : geometrical algorithms\\n"
245     "  GeomAPI             : geometrical data structures\\n"
246     "  GeomDataAPI         : specific geometrical data structures stored in the data model\\n"
247     "  ModelAPI            : general low-level interface to access data model\\n"
248     "  ModelHighAPI        : general high-level interface to access data model\\n"
249     "  ParametersAPI       : Parameters plugin for parameters feature management\\n"
250     "  PartSetAPI          : PartSet plugin for management Parts features\\n"
251     "  SketchAPI           : Sketch plugin with all sketch features\")";
252
253   PyRun_SimpleString(aHelpTxt);
254
255   return PyRun_SimpleString("from math import *") == 0;
256 }
257
258 void InitializationPlugin_PyInterp::closeContext()
259 {
260   Py_XDECREF(_local_context);
261   PyInterp_Interp::closeContext();
262 }
263
264 bool InitializationPlugin_PyInterp::runString(std::string theString)
265 {
266   PyLockWrapper lck; // Acquire GIL until the end of the method
267   return PyRun_SimpleString(theString.c_str());
268 }