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