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