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