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