Salome HOME
print et except
[tools/eficas.git] / Extensions / interpreteur_formule.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2013   EDF R&D
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20 import string,re,sys,types
21
22 from Noyau.N_CR import CR
23 from Extensions.i18n import tr
24
25
26 #def group(*choices): return '(' + string.join(choices, '|') + ')'
27 #def any(*choices): return apply(group, choices) + '*'
28 #def maybe(*choices): return apply(group, choices) + '?'
29
30 Intnumber = r'[1-9]\d*'
31 Exponent = r'[eEdD][-+]?\d+'
32 Expfloat = r'[1-9]\d*' + Exponent
33 #Pointfloat = group(r'\d+\.\d*', r'\.\d+') + maybe(Exponent)
34 #Floatnumber = group(Pointfloat, Expfloat)
35 Pointfloat=r'(\d+\.\d*|\.\d+)([eEdD][-+]?\d+)?'
36 Floatnumber=r'((\d+\.\d*|\.\d+)([eEdD][-+]?\d+)?|[1-9]\d*[eEdD][-+]?\d+)'
37
38
39 pat_number = re.compile(r'^([+-]?)([0-9]+)(\.\d*)?(.*)')
40 pat_number_complet = re.compile(r'^([+-]?)([0-9]+)(\.\d*)?([eEdD][+-]?\d+)(.*)')
41 pat_constante = re.compile(r'^([+-]?)([a-zA-Z][a-zA-Z_0-9]*\s*)(.*)')
42
43 def cmp_function(arg1,arg2):
44     """
45     Fonction de comparaison permettant de classer les listes de
46     fonctions unaires et binaires selon la longueur de leurs arguments
47     On classe les arguments les plus longs en premier
48     """
49     if len(arg1) > len(arg2):
50         return -1
51     elif len(arg1) == len(arg2):
52         return 0
53     else:
54         return 1
55     
56 class InterpreteurException(Exception):
57     """
58     Classe servant a definir les exceptions levees par l'interpreteur de formule
59     """
60     def __init__(self,args=None):
61         self.args = args
62
63     def __str__(self):
64         return str(self.args)
65
66 class Interpreteur_Formule:
67     """
68     Cette classe sert a construire un interpreteur de formules Aster
69     """
70     l_fonctions_binaires = ['+','-','*','/','**','=','MOD','MIN','MAX','ATAN2']
71     l_fonctions_unaires = ['+','-','INT','REAL','AIMAG','ABS','SQRT','EXP','LOG',
72                            'LOG10','SIN','COS','TAN','ASIN','ACOS','ATAN','SINH',
73                            'COSH','TANH','HEAVYSID']
74     l_constantes = ['PI','RD_RG','DG_RD']
75  
76     def __init__(self,formule=None,constantes=[],fonctions=[],parent=None):
77         """
78         Constructeur d'interpreteurs de formule Aster
79         - formule = tuple (nom,type,arguments,corps)
80         - constantes = liste des noms de constantes externes
81         - fonctions_unaires = dictionnaire {nom_fonction externe : nb arguments de cette fonction}
82         """
83         self.new_constantes = constantes
84         self.new_fonctions_unaires = fonctions
85         self.cr = CR()
86         self.l_operateurs = []
87         self.parent = parent
88         self.l_children = []
89         if formule :
90             self.set_formule(formule)
91         if self.parent :
92             self.parent.enregistre(self)
93
94     def set_formule(self,formule):
95         """
96         Stocke formule (tuple) dans l'attribut t_formule
97         Methode externe
98         """
99         #if type(formule) != types.TupleType:
100         if type(formule) != types.tuple:
101             raise InterpreteurException(tr("La formule passee a l'interpreteur doit etre sous forme de tuple"))
102         self.t_formule = formule
103         #self.init_cr()
104         self.modify_listes()
105         self.ordonne_listes()
106
107     def init_cr(self):
108         """
109         Initialise le cr,cad valorise les chaines debut et fin
110         """
111         nom = self.t_formule[0]
112         if nom :
113             if nom[0] in ('+','-') : nom = nom[1:]
114         self.cr.debut = tr("Debut Fonction %s", nom)
115         self.cr.fin = tr("Fin Fonction %s", nom)
116         
117     def str(self):
118         """
119         Retourne une liste de chaines de caracteres representant la formule
120         """
121         l_txt = []
122         l_txt.append(self.t_formule[0])
123         for oper in self.l_operateurs:
124             # oper est ici une liste decrivant oper
125             txt = []
126             for elem in oper:
127                 txt.append(str(elem))
128             l_txt.append(txt)
129         return l_txt
130
131     def report(self,decalage=1):
132         """
133         Retourne le rapport de FORMULE
134         """
135         txt = self.cr.report()
136         return txt
137     
138     def enregistre(self,fils):
139         """
140         Enregistre un operateur fils dans la liste des children
141         """
142         self.l_children.append(fils)
143         self.cr.add(fils.cr)
144         
145     def isvalid(self):
146         """
147         Booleenne qui retourne 1 si la formule est valide, 0 sinon
148         Methode externe
149         """
150         self.l_operateurs = []
151         self.cr.purge() # on vide le cr 
152         self.init_cr() # on initialise le cr
153         self.interprete_formule()
154         return self.cr.estvide()
155
156     def interprete_formule(self):
157         """
158         Realise l'interpretation du corps de la formule
159         """
160         texte = self.t_formule[3]
161         if not texte : return
162         if type(texte) != types.ListType:
163             texte = [texte,]
164         for text_arg in texte:
165             text_arg = string.replace(text_arg,'\n','')
166             # Enleve les espaces
167             text_arg = string.replace(text_arg,' ','')
168             try:
169                 self.l_operateurs.append(self.split_operateurs(text_arg))
170             except InterpreteurException as e:
171                 self.cr.fatal(e.__str__())
172
173     def modify_listes(self):
174         """
175         Modifie la liste des constantes en lui ajoutant le nom des parametres
176         de la fonction a interpreter
177         """
178         args = self.t_formule[2]
179         # l'interpreteur de formule sert aussi a evaluer les EVAL
180         # dans ce cas il n'y a pas d'arguments puisque pas de fonction ...
181         if args :
182             args = args[1:-1] # on enleve les parentheses ouvrante et fermante
183             l_args = string.split(args,',')
184             for arg in l_args:
185                 typ,nom = string.split(arg,':')
186                 nom = string.strip(nom)
187                 self.l_constantes.append(nom)
188         # on considere que les fonctions unaires de base sont toutes a un seul argument :
189         l_f = []
190         self.d_fonctions_unaires = {}
191         for fct in self.l_fonctions_unaires:
192             self.d_fonctions_unaires[fct]=1
193         # on ajoute les constantes externes
194         for cte in self.new_constantes:
195             self.l_constantes.append(cte)
196         # on ajoute les fonctions unaires externes au dictionnaire des fonctions unaires
197         for new_fonc in self.new_fonctions_unaires:
198             self.d_fonctions_unaires[new_fonc[0]] = self.get_nb_args(new_fonc)
199         #self.d_fonctions_unaires.update(self.new_fonctions_unaires)
200         self.l_fonctions_unaires = self.d_fonctions_unaires.keys()
201         
202     def ordonne_listes(self):
203         """
204         Ordonne les listes de fonctions unaires et binaires
205         """
206         self.l_fonctions_binaires.sort(cmp_function)
207         self.l_fonctions_unaires.sort(cmp_function)
208         self.l_constantes.sort(cmp_function)
209         
210
211     def split_operateurs(self,texte):
212         """
213         Splite le texte passe en argument en operateurs plus elementaires.
214         N'analyse pas l'interieur des operateurs (ne fait qu'une passe)
215         """
216         l_operateurs = []
217         texte = string.strip(texte)
218         # on recherche un nombre en debut de texte
219         try:
220             oper,reste = self.cherche_nombre(texte)
221         except InterpreteurException as e:
222             raise InterpreteurException (e.__str__())
223         if not oper :
224             # on recherche une constante en debut de texte
225             try:
226                 oper,reste = self.cherche_constante(texte)
227             except InterpreteurException as e:
228                 raise InterpreteurException (e.__str__())
229             if not oper :
230                 # on recherche une expression entre parentheses...
231                 try:
232                     oper,reste = self.cherche_expression_entre_parentheses(texte)
233                 except InterpreteurException as e:
234                     raise InterpreteurException(e.__str__())
235                 if not oper :
236                     # on recherche le debut d'un operateur unaire en debut de texte
237                     try:
238                         oper,reste = self.cherche_operateur_unaire(texte)
239                     except InterpreteurException as e:
240                         raise InterpreteurException(e.__str__())
241                     if not oper :
242                         type_objet,nom_objet = self.get_type(texte)
243                         if type_objet == 'constante':
244                             raise InterpreteurException( "Constante %s inconnue" %nom_objet)
245                         elif type_objet == 'fonction':
246                             raise InterpreteurException( "Fonction %s inconnue dans %s" %(nom_objet,texte))
247                         else:
248                             raise InterpreteurException( "Impossible d'interpreter : %s" %texte)
249         # on a trouve un operateur (nombre, constante ou unaire)
250         # il faut encore verifier que l'on est en fin de texte ou qu'il est bien suivi
251         # d'un operateur binaire
252         l_operateurs.append(oper)
253         if reste :
254             texte = string.strip(reste)
255             oper,reste = self.cherche_operateur_binaire(texte)
256             if not oper :
257                 # on a un reste et pas d'operateur binaire --> erreur
258                 raise InterpreteurException("L'operateur %s doit etre suivi d'un operateur binaire" %l_operateurs[-1])
259             else:
260                 # on a bien trouve un operateur binaire:
261                 l_operateurs.append(oper)
262                 # il faut recommencer l'analyse du reste par split_operateurs ...
263                 try:
264                     l_op = self.split_operateurs(reste)
265                 except InterpreteurException as e:
266                     raise InterpreteurException(e.__str__())
267                 l_operateurs.extend(l_op)
268                 return l_operateurs
269         else:
270             # on a fini d'analyser texte
271             return l_operateurs
272
273     def cherche_nombre(self,texte):
274         """
275         Cherche un nombre en debut de texte
276         Retourne ce nombre et le reste ou None et le texte initial
277         Peut lever une InterpreteurException dans le cas ou le nombre n'est pas valide
278         """
279         texte = string.strip(texte)
280         m = pat_number_complet.match(texte)
281         if m:
282             # on a trouve un nombre avec exposant
283             l_groups = m.groups()
284             sgn = l_groups[0]
285             nb = l_groups[1]
286             if l_groups[2]:
287                 nb = nb+l_groups[2]
288             if l_groups[3]:
289                 nb = nb+l_groups[3]
290             nombre = sgn+nb
291             return nombre,l_groups[4]
292         else:
293             m = pat_number.match(texte)
294             if m :
295                 # on a trouve un nombre sans exposant
296                 l_groups = m.groups()
297                 sgn = l_groups[0]
298                 nb = l_groups[1]
299                 if l_groups[2]:
300                     nb = nb+l_groups[2]
301                 nombre = sgn+nb
302                 # il faut verifier si ce nombre n'est pas suivi d'un exposant incomplet ...
303                 reste = string.strip(l_groups[3])
304                 if reste == '':
305                     return nombre,l_groups[3]
306                 if reste[0] in ('e','E','d','D') :
307                     raise InterpreteurException("La syntaxe de l'exposant de %s est erronee " %nb)
308                 else:
309                     return nombre,l_groups[3]
310             else:
311                 # on n'a pas trouve de nombre
312                 return None,texte
313         
314     def cherche_constante_old(self,texte):
315         """
316         Recherche une constante en debut de texte parmi la liste des constantes.
317         Retourne le texte representant la constante et le reste du texte ou
318         Retourne None,texte si aucune constante trouvee
319         """
320         txt = None
321         texte = string.strip(texte)
322         for cte in self.l_constantes:
323             index = string.find(texte,cte)
324             #if index == 0 : print 'on a trouve %s dans %s en %d' %(cte,texte,index)
325             if index == 0 :
326                 txt = cte
327                 zz,reste = string.split(texte,cte,1)
328                 break
329         if txt :
330             return txt,reste
331         else:
332             # aucune constante trouvee
333             return None,texte
334
335     def cherche_constante(self,texte):
336         """
337         Recherche une constante en debut de texte parmi la liste des constantes.
338         Retourne le texte representant la constante et le reste du texte ou
339         Retourne None,texte si aucune constante trouvee
340         """
341         txt = None
342         texte = string.strip(texte)
343         m = pat_constante.match(texte)
344         if m :
345             # on a trouve un identificateur en debut de texte
346             l_groups = m.groups()
347             sgn = l_groups[0]
348             identificateur = string.strip(l_groups[1])
349             reste = l_groups[2]
350             # il faut verifier qu'il ne s'agit pas d'un appel a une fonction
351             if reste :
352                 if reste[0] == '(' :
353                     # --> appel de fonction
354                     return None,texte
355             # il faut encore verifier qu'elle est bien dans la liste des constantes...
356             if identificateur not in self.l_constantes :
357                 raise InterpreteurException("La constante %s est inconnue dans %s" %(identificateur,texte))
358             else:
359                 return sgn+identificateur,reste
360         else:
361             # aucune constante trouvee
362             return None,texte
363         
364     def cherche_args(self,texte):
365         """
366         Cherche au debut de texte une liste d'arguments entre parentheses
367         """
368         if texte[0]!='(':
369             return None,texte
370         else:
371             n=0
372             cpt=1
373             while cpt != 0:
374                 n=n+1
375                 if n>= len(texte):
376                     # on a atteint la fin de texte sans avoir trouve la parenthese fermante --> erreur
377                     raise InterpreteurException("Manque parenthese fermante dans %s" %texte)
378                 if texte[n] == '(':
379                     cpt=cpt+1
380                 elif texte[n]==')':
381                     cpt=cpt-1
382             if (n+1 < len(texte)):
383                 return texte[0:n+1],texte[n+1:]
384             else:
385                 # on a fini d'analyser le texte : reste = None
386                 return texte,None
387                     
388     def cherche_operateur_unaire_old(self,texte):
389         """
390         Cherche dans texte un operateur unaire
391         """
392         txt = None
393         texte = string.strip(texte)
394         for oper in self.l_fonctions_unaires:
395             index = string.find(texte,oper)
396             if index == 0 :
397                 txt = oper
398                 zz,reste = string.split(texte,oper,1)
399                 break
400         if txt :
401             #print 'on a trouve :',txt
402             operateur = txt
403             texte = reste
404             try:
405                 args,reste = self.cherche_args(texte)
406             except InterpreteurException as e:
407                 raise InterpreteurException(e.__str__())
408             if not args :
409                 # operateur unaire sans arguments
410                 raise InterpreteurException('operateur unaire  %s sans arguments' %operateur)
411             else:
412                 #operateur = operateur+args
413                 args = self.split_args(txt,args,self.d_fonctions_unaires[operateur])
414                 formule_operateur = (txt,'',self.t_formule[2],args)
415                 operateur = Interpreteur_Formule(formule = formule_operateur,
416                                                  constantes = self.new_constantes,
417                                                  fonctions_unaires = self.new_fonctions_unaires,
418                                                  parent = self)
419                 operateur.interprete_formule()
420                 texte = reste
421                 return operateur,reste
422         else:
423             # aucun operateur unaire trouve
424             return None,texte
425
426     def cherche_operateur_unaire(self,texte):
427         """
428         Cherche dans texte un operateur unaire
429         """
430         txt = None
431         texte = string.strip(texte)
432         m = pat_constante.match(texte)
433         if m :
434             # on a trouve un identificateur en debut de texte
435             # il faut encore verifier que l'on a bien a faire a un appel de fonction ...
436             l_groups = m.groups()
437             sgn = l_groups[0]
438             identificateur = string.strip(l_groups[1])
439             reste = l_groups[2]
440             try:
441                 args,reste = self.cherche_args(reste)
442             except InterpreteurException as e:
443                 raise InterpreteurException (e.__str__())
444             if not args :
445                 # operateur unaire sans arguments
446                 # en principe on ne doit jamais etre dans ce cas car il est deja trappe par cherche_constante ...
447                 raise InterpreteurException ('Fonction %s sans arguments !' %identificateur)
448             else:
449                 # il faut encore verifier que l'on a bien a faire a une fonction connue
450                 if identificateur not in self.l_fonctions_unaires:
451                     raise InterpreteurException ('Fonction %s inconnue dans %s !' %(identificateur,texte))
452                 args = self.split_args(identificateur,args,self.d_fonctions_unaires[identificateur])
453                 formule_operateur = (sgn+identificateur,'',self.t_formule[2],args)
454                 operateur = Interpreteur_Formule(formule = formule_operateur,
455                                                  constantes = self.new_constantes,
456                                                  fonctions = self.new_fonctions_unaires,
457                                                  parent = self)
458                 operateur.interprete_formule()
459                 texte = reste
460                 return operateur,reste
461         elif texte[0] == '-':
462             # Il faut pouvoir trapper les expressions du type exp(-(x+1)) ...
463             try :
464                args,reste = self.cherche_args(texte[1:])
465             except InterpreteurException as e:
466                 raise InterpreteurException (e.__str__())
467             if not args :
468                # Il ne s'agit pas de '-' comme operateur unaire --> on retourne None
469                return None,texte
470             else:
471                identificateur = '-'
472                args = self.split_args(identificateur,args,self.d_fonctions_unaires[identificateur])
473                formule_operateur = (identificateur,'',self.t_formule[2],args)
474                operateur = Interpreteur_Formule(formule = formule_operateur,
475                                                  constantes = self.new_constantes,
476                                                  fonctions = self.new_fonctions_unaires,
477                                                  parent = self)
478                operateur.interprete_formule()
479                texte = reste
480                return operateur,reste
481         else:
482             return None,texte
483             
484     def cherche_operateur_binaire(self,texte):
485         """
486         Cherche dans texte un operateur unaire
487         """
488         txt = None
489         texte = string.strip(texte)
490         for oper in self.l_fonctions_binaires:
491             index = string.find(texte,oper)
492             #if index != -1 : print 'on a trouve %s dans %s en %d' %(oper,texte,index)
493             if index == 0 :
494                 txt = oper
495                 zz,reste = string.split(texte,oper,1)
496                 break
497         if txt :
498             return txt,reste
499         else:
500             # aucun operateur unaire trouve
501             return None,texte
502
503     def cherche_expression_entre_parentheses(self,texte):
504         """
505         Cherche en debut de texte une expression entre parentheses
506         """
507         args,reste = self.cherche_args(string.strip(texte))
508         if not args :
509             return None,texte
510         else:
511             # on a trouve une expression entre parentheses en debut de texte
512             # --> on retourne un objet Interpreteur_Formule
513             formule_operateur = ('','',self.t_formule[2],args[1:-1])
514             operateur = Interpreteur_Formule(formule = formule_operateur,
515                                              constantes = self.new_constantes,
516                                              fonctions = self.new_fonctions_unaires,
517                                              parent = self)
518             operateur.interprete_formule()
519             texte = reste
520             return operateur,reste
521             
522     def split_args(self,nom_fonction,args,nb_args):
523         """
524         Tente de partager args en nb_args elements
525         Retourne une liste de chaines de caracteres (liste de longueur nb_args)
526         """
527         args = args[1:-1] # on enleve les parentheses ouvrante et fermante
528         if nb_args == 1 : return args
529         l_args = string.split(args,',')
530         if len(l_args) != nb_args:
531             raise InterpreteurException  ("La fonction %s requiert %d arguments : %d fourni(s)" %(nom_fonction,nb_args,len(l_args)))
532         else:
533             return l_args
534
535     def get_type(self,texte):
536         """
537         Retourne le type de l'objet defini dans texte, a savoir:
538         - constante
539         - fonction
540         - unknown
541         et son nom
542         """
543         texte = string.strip(texte)
544         if '(' not in texte:
545             return 'constante',texte
546         if texte[-1] != ')':
547             return 'unknown',''
548         nom_oper,args = string.split(texte,'(',1)
549         return 'fonction',nom_oper
550
551     def get_nb_args(self,formule):
552         """
553         Retourne le nombre d'arguments dans la definition de formule (sous forme de tuple)
554         """
555         args = formule[2][1:-1] # on enleve les parentheses ouvrante et fermante
556         l_args = string.split(args,',')
557         return len(l_args)
558
559 if __name__ == '__main__':
560     constantes = ['FREQ3','AMOR1']
561     fonctions_unaires=[('ACC','REEL','(REEL:x)','''bidon'''),]
562     f1 = ('f1','REEL','(REEL:x)','''SIN(x)+3*x''')
563     f2 = ('f2','REEL','(REEL:x)','''ATAN(x+3)+3*x''')
564     f3 = ('f3','REEL','(REEL:INST)','''ACC(INST,FREQ3,AMOR1)''')
565     f4 = ('f4','REEL','(REEL:INST)','''ACC(INST,FREQ2,AMOR1)''')
566     f5 = ('f5','REEL','(REEL:INST,REEL:Y)','''ACC(INST,FREQ3,AMOR1)+Y*INST''')
567     f6 = ('f6','REEL','(REEL:x)','''(x+ 3)/ 35.698''')
568     f7 = ('f7','REEL','(REEL:x)','''(x+ 3)/ 35.698E-10''')
569     f8 = ('f8','REEL','(REEL:x)','''(x+ 3)/ 35.698E''')
570     f9 = ('f9','REEL','(REEL:INSTA,REEl:INSTB)','''2.*SIN((PI/4)+((INSTA-INSTB)/2.))* COS((PI/4)-((INSTA+INSTB)/2.))''')
571     f10 = ('f10','REEL','(REEL:X)','''EXP(-(X+1))''')
572     for formule in (f1,f2,f3,f4,f5,f6,f7,f8,f9,f10):
573         i = Interpreteur_Formule(formule = formule,
574                                  constantes = constantes,
575                                  fonctions = fonctions_unaires)
576         txt = i.str()
577         print ('\nformule %s = %s' %(str(formule),txt))
578         #if i.isvalid() :
579         #    print "\n\tPas d'erreur !"
580         #else:
581         #    print i.report()