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