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