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