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