Salome HOME
Copyright update 2021
[modules/yacs.git] / src / py2yacs / py2yacs.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 *-
3 # Copyright (C) 2007-2021  CEA/DEN, EDF R&D
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 import ast
22
23 class FunctionProperties:
24   def __init__(self, function_name):
25     self.name = function_name
26     self.inputs=[]
27     self.outputs=None
28     self.errors=[]
29     self.imports=[]
30     pass
31   def __str__(self):
32     result = "Function:" + self.name + "\n"
33     result+= "  Inputs:" + str(self.inputs) + "\n"
34     result+= "  Outputs:"+ str(self.outputs) + "\n"
35     result+= "  Errors:" + str(self.errors) + "\n"
36     result+= "  Imports:"+ str(self.imports) + "\n"
37     return result
38
39 class VisitAST(ast.NodeVisitor):
40   def visit_Module(self, node):
41     accepted_tokens = ["Import", "ImportFrom", "FunctionDef", "ClassDef", "If"]
42     self.global_errors=[]
43     for e in node.body:
44       type_name = type(e).__name__
45       if type_name not in accepted_tokens:
46         error="py2yacs error at line %s: not accepted statement '%s'." % (
47                e.lineno, type_name)
48         self.global_errors.append(error)
49     self.functions=[]
50     self.lastfn=""
51     self.infunc=False
52     self.inargs=False
53     self.generic_visit(node)
54     pass
55   def visit_FunctionDef(self, node):
56     if not self.infunc:
57       self.lastfn = FunctionProperties(node.name)
58       self.functions.append(self.lastfn)
59       self.infunc=True
60       #
61       self.generic_visit(node)
62       #
63       self.lastfn = None
64       self.infunc=False
65     pass
66   def visit_arg(self, node):
67     self.lastfn.inputs.append(node.arg)
68     pass
69   def visit_Return(self, node):
70     if self.lastfn.outputs is not None :
71       error="py2yacs error at line %s: multiple returns." % node.lineno
72       self.lastfn.errors.append(error)
73       return
74     self.lastfn.outputs = []
75     if node.value is None :
76       pass
77     elif 'Tuple' == type(node.value).__name__ :
78       for e in node.value.elts:
79         if 'Name' == type(e).__name__ :
80           self.lastfn.outputs.append(e.id)
81         else :
82           error="py2yacs error at line %s: invalid type returned '%s'." % (
83                   node.lineno, type(e).__name__)
84           self.lastfn.errors.append(error)
85     else:
86       if 'Name' == type(node.value).__name__ :
87         self.lastfn.outputs.append(node.value.id)
88       else :
89         error="py2yacs error at line %s: invalid type returned '%s'." %(
90                   node.lineno, type(node.value).__name__)
91         self.lastfn.errors.append(error)
92         pass
93       pass
94     pass
95
96   def visit_ClassDef(self, node):
97     # just ignore classes
98     pass
99
100   def visit_Import(self, node):
101     if self.infunc:
102       for n in node.names:
103         self.lastfn.imports.append(n.name)
104   def visit_ImportFrom(self, node):
105     if self.infunc:
106       m=node.module
107       for n in node.names:
108         self.lastfn.imports.append(m+"."+n.name)
109
110 class vtest(ast.NodeVisitor):
111   def generic_visit(self, node):
112     ast.NodeVisitor.generic_visit(self, node)
113
114 def create_yacs_schema(text, fn_name, fn_args, fn_returns, file_name):
115   import pilot
116   import SALOMERuntime
117   SALOMERuntime.RuntimeSALOME_setRuntime()
118   runtime = pilot.getRuntime()
119   schema = runtime.createProc("schema")
120   node = runtime.createScriptNode("", "default_name")
121   schema.edAddChild(node)
122   fncall = "\n%s=%s(%s)\n"%(",".join(fn_returns),
123                             fn_name,
124                             ",".join(fn_args))
125   node.setScript(text+fncall)
126   td=schema.getTypeCode("double")
127   for p in fn_args:
128     newport = node.edAddInputPort(p, td)
129     newport.edInit(0.0)
130   for p in fn_returns:
131     node.edAddOutputPort(p, td)
132   myContainer=schema.createContainer("Py2YacsContainer")
133   node.setExecutionMode(pilot.InlineNode.REMOTE_STR)
134   node.setContainer(myContainer)
135   schema.saveSchema(file_name)
136
137 def get_properties(text_file):
138   try:
139     bt=ast.parse(text_file)
140   except SyntaxError as err:
141     import traceback
142     return [], ["".join(traceback.format_exception_only(SyntaxError,err))]
143   w=VisitAST()
144   w.visit(bt)
145   return w.functions, w.global_errors
146
147 def function_properties(python_path, fn_name):
148   """
149   python_path : path to a python file
150   fn_name : name of a function in the file
151   return : properties of the function. see class FunctionProperties
152   """
153   with open(python_path, 'r') as f:
154     text_file = f.read()
155   functions,errors = get_properties(text_file)
156   result = [fn for fn in functions if fn.name == fn_name]
157   if len(result) < 1:
158     raise Exception("Function not found: {}".format(fn_name))
159   result = result[0]
160   error_string = ""
161   if len(errors) > 0:
162     error_string += "Global errors in file {}\n".format(python_path)
163     error_string += '\n'.join(errors)
164     raise Exception(error_string)
165   if len(result.errors) > 0:
166     error_string += "Errors when parsing function {}\n".format(fn_name)
167     error_string += '\n'.join(result.errors)
168     raise Exception(error_string)
169   return result
170
171
172 def main(python_path, yacs_path, function_name="_exec"):
173   with open(python_path, 'r') as f:
174     text_file = f.read()
175   fn_name = function_name
176   functions,errors = get_properties(text_file)
177   error_string = ""
178   if len(errors) > 0:
179     error_string += "global errors:\n"
180     error_string += '\n'.join(errors)
181     return error_string
182   fn_properties = next((f for f in functions if f.name == fn_name), None)
183   if fn_properties is not None :
184     if not fn_properties.errors :
185       create_yacs_schema(text_file, fn_name,
186                          fn_properties.inputs, fn_properties.outputs,
187                          yacs_path)
188     else:
189       error_string += '\n'.join(fn_properties.errors)
190   else:
191     error_string += "Function not found:"
192     error_string += fn_name
193   return error_string
194
195 if __name__ == '__main__':
196   import argparse
197   parser = argparse.ArgumentParser(description="Generate a YACS schema from a python file containing a function to run.")
198   parser.add_argument("file", help='Path to the python file')
199   parser.add_argument("-o","--output",
200         help='Path to the output file (yacs_schema.xml by default)',
201         default='yacs_schema.xml')
202   parser.add_argument("-d","--def_name",
203         help='Name of the function to call in the yacs node (_exec by default)',
204         default='_exec')
205   args = parser.parse_args()
206   erreurs = main(args.file, args.output, args.def_name)
207   import sys
208   if len(erreurs) > 0:
209     print(erreurs)
210     sys.exit(1)
211   else:
212     sys.exit(0)