Salome HOME
2850b80c263c554719d20a56e741b51bd984cfea
[modules/yacs.git] / src / py2yacs / py2yacs.cxx
1 // Copyright (C) 2006-2024  CEA, EDF
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 #include <Python.h>
20 #include <sstream>
21 #include "py2yacs.hxx"
22 #include "RuntimeSALOME.hxx"
23 #include "Proc.hxx"
24 #include "InlineNode.hxx"
25 #include "PythonCppUtils.hxx"
26 #include "InputPort.hxx"
27 #include "Container.hxx"
28
29 Py2yacsException::Py2yacsException(const std::string& what)
30 : std::exception(),
31   _what(what)
32 {
33 }
34
35 Py2yacsException::~Py2yacsException() noexcept
36 {
37 }
38
39 const char * Py2yacsException::what() const noexcept
40 {
41   return _what.c_str();
42 }
43
44
45 Py2yacs::Py2yacs()
46 : _python_parser_module("py2yacs"),
47   _python_parser_function("get_properties"),
48   _functions(),
49   _global_errors(),
50   _python_code()
51 {
52 }
53
54 Py2yacs::Py2yacs(const std::string& python_parser_module,
55         const std::string& python_parser_function)
56 : _python_parser_module(python_parser_module),
57   _python_parser_function(python_parser_function),
58   _functions(),
59   _global_errors(),
60   _python_code()
61 {
62 }
63
64 const std::list<std::string>& Py2yacs::getGlobalErrors() const
65 {
66   return _global_errors;
67 }
68
69 const std::list<FunctionProperties>& Py2yacs::getFunctionProperties()const
70 {
71   return _functions;
72 }
73
74 // Copy a python list of strings to a c++ list of strings.
75 // Return an error string. An empty string means no error.
76 static
77 std::string copyList(PyObject *pyList, std::list<std::string>& cppList)
78 {
79   std::string error="";
80   if(!PyList_Check(pyList))
81   {
82     error = "Not a python list.\n";
83     //throw Py2yacsException("Not a python list.");
84   }
85   else
86   {
87     Py_ssize_t n = PyList_Size(pyList);
88     for(Py_ssize_t i=0; i<n; i++)
89     {
90       PyObject *elem = PyList_GetItem(pyList,i);
91       if(!PyUnicode_Check(elem))
92       {
93         std::stringstream message;
94         message << "List element number " << i << " is not a string.\n";
95         error += message.str();
96         // throw Py2yacsException(message.str());
97       }
98       else
99       {
100         cppList.push_back(std::string(PyUnicode_AsUTF8(elem)));
101       }
102     }
103   }
104   return error;
105 }
106
107 static
108 std::string getPyErrorText()
109 {
110   std::string result="";
111   if (PyErr_Occurred())
112   {
113     PyObject *ptype, *pvalue, *ptraceback;
114     PyObject *pystr, *module_name, *pyth_module, *pyth_func;
115     PyErr_Fetch(&ptype, &pvalue, &ptraceback);
116     pystr = PyObject_Str(pvalue);
117     result = std::string(PyUnicode_AsUTF8(pystr));
118     result += "\n";
119     Py_DECREF(pystr);
120     
121     /* See if we can get a full traceback */
122     if(ptraceback)
123     {
124       module_name = PyUnicode_FromString("traceback");
125       pyth_module = PyImport_Import(module_name);
126       Py_DECREF(module_name);
127       if (pyth_module)
128       {
129         pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
130         if (pyth_func && PyCallable_Check(pyth_func))
131         {
132           PyObject *pyList;
133           pyList = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL);
134           if(pyList)
135           {
136             int n = PyList_Size(pyList);
137             for(int i=0; i<n; i++)
138             {
139               pystr = PyList_GetItem(pyList,i);
140               result += std::string(PyUnicode_AsUTF8(pystr));
141             }
142             Py_DECREF(pyList);
143           }
144         }
145         Py_XDECREF(pyth_func);
146         Py_DECREF(pyth_module);
147       }
148     }
149     Py_XDECREF(ptype);
150     Py_XDECREF(pvalue);
151     Py_XDECREF(ptraceback);
152   }
153   return result;
154 }
155
156 static
157 PyObject* checkAndGetAttribute(PyObject *p,
158                                const char* attribute,
159                                std::string& error)
160 {
161   PyObject *pAttribute = PyObject_GetAttrString(p, attribute);
162   if(!pAttribute)
163   {
164     error += "Attribute '";
165     error += attribute;
166     error += "' not found in the returned value of the parsing function.\n";
167     error += getPyErrorText();
168   }
169   return pAttribute;
170 }
171
172 void Py2yacs::load(const std::string& python_code)
173 {
174     PyObject *pModule, *pDict, *pFunc;
175     PyObject *pArgs, *pValue;
176     int i;
177     std::string errorMessage="";
178     _python_code = python_code;
179     _functions.clear();
180     _global_errors.clear();
181     
182     // Py_Initialize();
183     AutoGIL agil;
184     pValue = PyUnicode_FromString(_python_parser_module.c_str());
185     pModule = PyImport_Import(pValue);
186     Py_DECREF(pValue);
187
188     if (!pModule)
189     {
190       errorMessage  = getPyErrorText();
191       errorMessage += "\nFailed to load ";
192       errorMessage += _python_parser_module;
193       errorMessage += ".\n";
194     }
195     else
196     {
197       pFunc = PyObject_GetAttrString(pModule, _python_parser_function.c_str());
198
199       if (pFunc && PyCallable_Check(pFunc))
200       {
201         pArgs = PyTuple_New(1);
202         pValue = PyUnicode_FromString(python_code.c_str());
203         PyTuple_SetItem(pArgs, 0, pValue);
204         
205         pValue = PyObject_CallObject(pFunc, pArgs);
206         Py_DECREF(pArgs);
207         if (!pValue)
208             errorMessage = getPyErrorText();
209         else
210         {
211           if (!PyTuple_Check(pValue))
212           {
213             errorMessage += "Parsing function should return a tuple of two string lists.\n";
214           }
215           Py_ssize_t n = PyTuple_Size(pValue);
216           if(n != 2)
217           {
218             errorMessage += "Parsing function should return two string lists.\n";
219           }
220           PyObject *pyList = PyTuple_GetItem(pValue, 0);
221           if(!PyList_Check(pyList))
222           {
223             errorMessage += "The first returned value of the parsing function"
224                             " should be a python list.\n";
225           }
226           else
227           {
228             n = PyList_Size(pyList);
229             for(int i=0; i<n; i++)
230             {
231               PyObject *fpy = PyList_GetItem(pyList,i);
232               PyObject *pAttribute;
233               
234               if(pAttribute = checkAndGetAttribute(fpy, "name", errorMessage))
235               {
236                 if(!PyUnicode_Check(pAttribute))
237                 {
238                   errorMessage += "Attribute 'name' should be a string.\n";
239                   Py_DECREF(pAttribute);
240                 }
241                 else
242                 {
243                   _functions.push_back(FunctionProperties());
244                   FunctionProperties& fcpp = _functions.back();
245                   fcpp._name=std::string(PyUnicode_AsUTF8(pAttribute));
246                   Py_DECREF(pAttribute);
247                   
248                   if(pAttribute = checkAndGetAttribute(fpy, "inputs", errorMessage))
249                     errorMessage += copyList(pAttribute, fcpp._input_ports);
250                   Py_XDECREF(pAttribute);
251                   
252                   if(pAttribute = checkAndGetAttribute(fpy, "outputs", errorMessage))
253                     // None value means no return statement in the function
254                     if(pAttribute != Py_None)
255                       errorMessage += copyList(pAttribute,fcpp._output_ports);
256                   Py_XDECREF(pAttribute);
257                     
258                   if(pAttribute = checkAndGetAttribute(fpy, "errors", errorMessage))
259                     errorMessage += copyList(pAttribute, fcpp._errors);
260                   Py_XDECREF(pAttribute);
261                   
262                   if(pAttribute = checkAndGetAttribute(fpy, "imports", errorMessage))
263                     errorMessage += copyList(pAttribute, fcpp._imports);
264                   Py_XDECREF(pAttribute);
265                 }
266               }
267             }
268           }
269           errorMessage += copyList(PyTuple_GetItem(pValue, 1), _global_errors);
270           Py_DECREF(pValue);
271         }
272       }
273       else
274       {
275         errorMessage  = getPyErrorText();
276         errorMessage += "\nCannot find the parsing function '";
277         errorMessage += _python_parser_function;
278         errorMessage += "' in python module '";
279         errorMessage += _python_parser_module;
280         errorMessage += "'.\n";
281       }
282       Py_XDECREF(pFunc);
283       Py_DECREF(pModule);
284     }
285     
286     if(!errorMessage.empty())
287       throw Py2yacsException(errorMessage);
288     // Py_Finalize();
289 }
290
291 void Py2yacs::save(const std::string& file_path,
292                    const std::string& python_function)const
293 {
294   YACS::ENGINE::Proc* schema = createProc(python_function);
295   schema->saveSchema(file_path);
296   delete schema;
297 }
298
299 YACS::ENGINE::Proc* Py2yacs::createProc(const std::string& python_function)const
300 {
301   if(!_global_errors.empty())
302   {
303     std::string error_message = "The python script contains errors.\n";
304     std::list<std::string>::const_iterator it;
305     for(it = _global_errors.begin(); it != _global_errors.end(); it++)
306       error_message += (*it) + "\n";
307     throw Py2yacsException(error_message);
308   }
309   
310   // find function properties
311   std::list<FunctionProperties>::const_iterator fn_prop = _functions.begin();
312   while(fn_prop != _functions.end() && fn_prop->_name != python_function)
313     fn_prop++;
314   
315   if(fn_prop == _functions.end())
316   {
317     throw Py2yacsException(std::string("Function not found: ")+python_function);
318   }
319   
320   if(!fn_prop->_errors.empty())
321   {
322     std::string error_message = "Function contains errors.\n";
323     std::list<std::string>::const_iterator it;
324     for(it = fn_prop->_errors.begin(); it != fn_prop->_errors.end(); it++)
325       error_message += (*it) + "\n";
326     throw Py2yacsException(error_message);
327   }
328   
329   // add the call to the function at the end of the script
330   std::stringstream fn_call;
331   fn_call << std::endl;
332   std::list<std::string>::const_iterator it;
333   bool first = true;
334   for(it=fn_prop->_output_ports.begin();
335       it!=fn_prop->_output_ports.end();
336       it++)
337   {
338     if (!first)
339       fn_call << ",";
340     first = false;
341     fn_call << *it;
342   }
343   fn_call << "=" << python_function << "(";
344   first = true;
345   for(it = fn_prop->_input_ports.begin();
346       it != fn_prop->_input_ports.end();
347       it++)
348   {
349     if (!first)
350       fn_call << ",";
351     first = false;
352     fn_call << *it;
353   }
354   fn_call << ")" << std::endl;
355   std::string node_body = _python_code + fn_call.str();
356   
357   YACS::ENGINE::Proc* schema;
358   YACS::ENGINE::RuntimeSALOME::setRuntime();
359   YACS::ENGINE::RuntimeSALOME* runtime = YACS::ENGINE::getSALOMERuntime();
360   
361   // build the YACS schema
362   const char * node_name = "default_name";
363   schema = runtime->createProc("Schema");
364   YACS::ENGINE::InlineNode* node = runtime->createScriptNode("", node_name);
365   schema->edAddChild(node);
366   node->setScript(node_body);
367   YACS::ENGINE::TypeCode *tc_double = runtime->getTypeCode("double");
368   
369   for(it = fn_prop->_input_ports.begin();
370       it != fn_prop->_input_ports.end();
371       it++)
372   {
373     YACS::ENGINE::InputPort *newport = node->edAddInputPort(*it, tc_double);
374     newport->edInit(0.0);
375   }
376   
377   for(it = fn_prop->_output_ports.begin();
378       it != fn_prop->_output_ports.end();
379       it++)
380     node->edAddOutputPort(*it, tc_double);
381
382   node->setExecutionMode(YACS::ENGINE::InlineNode::REMOTE_STR);
383   YACS::ENGINE::Container* cont=schema->createContainer("Py2YacsContainer");
384   node->setContainer(cont);
385   cont->decrRef();
386   return schema;
387 }
388
389 std::string Py2yacs::getAllErrors()const
390 {
391   std::stringstream buffer;
392   buffer.clear();
393   if(! _global_errors.empty())
394   {
395     buffer << "Global errors:" << std::endl;
396     std::list<std::string>::const_iterator it;
397     for(it=_global_errors.begin(); it!=_global_errors.end(); it++)
398     {
399       buffer << *it << std::endl;
400     }
401     buffer << "-----------------------------------------" << std::endl;
402   }
403
404   std::list<FunctionProperties>::const_iterator it_fp;
405   for(it_fp=_functions.begin();it_fp!=_functions.end();it_fp++)
406   {
407     if(! it_fp->_errors.empty())
408     {
409       buffer << "Function " << it_fp->_name << " has errors:" << std::endl;
410       std::list<std::string>::const_iterator it;
411       buffer << "Errors :" ;
412       for(it=it_fp->_errors.begin();it!=it_fp->_errors.end();it++)
413         buffer << *it << std::endl;
414       buffer << "-----------------------------------------" << std::endl;
415     }
416   }
417   return buffer.str();
418 }
419
420 std::string Py2yacs::getFunctionErrors(const std::string& functionName)const
421 {
422   std::stringstream buffer;
423   buffer.clear();
424   if(! _global_errors.empty())
425   {
426     buffer << "Global errors:" << std::endl;
427     std::list<std::string>::const_iterator it;
428     for(it=_global_errors.begin(); it!=_global_errors.end(); it++)
429     {
430       buffer << *it << std::endl;
431     }
432     buffer << "-----------------------------------------" << std::endl;
433   }
434
435   bool nameFound = false;
436   std::list<FunctionProperties>::const_iterator it_fp;
437   for(it_fp=_functions.begin(); it_fp!=_functions.end() && !nameFound; it_fp++)
438   {
439     if(it_fp->_name == functionName)
440     {
441       nameFound = true;
442       if(! it_fp->_errors.empty())
443       {
444         buffer << "Function " << it_fp->_name << " has errors:" << std::endl;
445         std::list<std::string>::const_iterator it;
446         buffer << "Errors :" ;
447         for(it=it_fp->_errors.begin();it!=it_fp->_errors.end();it++)
448           buffer << *it << std::endl;
449         buffer << "-----------------------------------------" << std::endl;
450       }
451     }
452   }
453
454   if(!nameFound)
455   {
456     buffer << "Function " << functionName << " not found." << std::endl;
457   }
458   return buffer.str();
459 }