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