1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2013 EDF R&D
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.
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.
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
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 import string,re,sys,exceptions,types
22 from Noyau.N_CR import CR
23 from Extensions.i18n import tr
26 def group(*choices): return '(' + string.join(choices, '|') + ')'
27 def any(*choices): return apply(group, choices) + '*'
28 def maybe(*choices): return apply(group, choices) + '?'
30 Intnumber = r'[1-9]\d*'
31 Exponent = r'[eEdD][-+]?\d+'
32 Pointfloat = group(r'\d+\.\d*', r'\.\d+') + maybe(Exponent)
33 Expfloat = r'[1-9]\d*' + Exponent
34 Floatnumber = group(Pointfloat, Expfloat)
36 pat_number = re.compile(r'^([+-]?)([0-9]+)(\.\d*)?(.*)')
37 pat_number_complet = re.compile(r'^([+-]?)([0-9]+)(\.\d*)?([eEdD][+-]?\d+)(.*)')
38 pat_constante = re.compile(r'^([+-]?)([a-zA-Z][a-zA-Z_0-9]*\s*)(.*)')
40 def cmp_function(arg1,arg2):
42 Fonction de comparaison permettant de classer les listes de
43 fonctions unaires et binaires selon la longueur de leurs arguments
44 On classe les arguments les plus longs en premier
46 if len(arg1) > len(arg2):
48 elif len(arg1) == len(arg2):
53 class InterpreteurException(exceptions.Exception):
55 Classe servant à définir les exceptions levées par l'interpréteur de formule
57 def __init__(self,args=None):
63 class Interpreteur_Formule:
65 Cette classe sert à construire un interpréteur de formules Aster
67 l_fonctions_binaires = ['+','-','*','/','**','=','MOD','MIN','MAX','ATAN2']
68 l_fonctions_unaires = ['+','-','INT','REAL','AIMAG','ABS','SQRT','EXP','LOG',
69 'LOG10','SIN','COS','TAN','ASIN','ACOS','ATAN','SINH',
70 'COSH','TANH','HEAVYSID']
71 l_constantes = ['PI','RD_RG','DG_RD']
73 def __init__(self,formule=None,constantes=[],fonctions=[],parent=None):
75 Constructeur d'interpréteurs de formule Aster
76 - formule = tuple (nom,type,arguments,corps)
77 - constantes = liste des noms de constantes externes
78 - fonctions_unaires = dictionnaire {nom_fonction externe : nb arguments de cette fonction}
80 self.new_constantes = constantes
81 self.new_fonctions_unaires = fonctions
83 self.l_operateurs = []
87 self.set_formule(formule)
89 self.parent.enregistre(self)
91 def set_formule(self,formule):
93 Stocke formule (tuple) dans l'attribut t_formule
96 if type(formule) != types.TupleType:
97 raise InterpreteurException,tr("La formule passee a l'interpreteur doit etre sous forme de tuple")
98 self.t_formule = formule
101 self.ordonne_listes()
105 Initialise le cr,cad valorise les chaînes debut et fin
107 nom = self.t_formule[0]
109 if nom[0] in ('+','-') : nom = nom[1:]
110 self.cr.debut = tr("Debut Fonction %s", nom)
111 self.cr.fin = tr("Fin Fonction %s", nom)
115 Retourne une liste de chaînes de caractères représentant la formule
118 l_txt.append(self.t_formule[0])
119 for oper in self.l_operateurs:
120 # oper est ici une liste décrivant oper
123 txt.append(str(elem))
127 def report(self,decalage=1):
129 Retourne le rapport de FORMULE
131 txt = self.cr.report()
134 def enregistre(self,fils):
136 Enregistre un opérateur fils dans la liste des children
138 self.l_children.append(fils)
143 Booléenne qui retourne 1 si la formule est valide, 0 sinon
146 self.l_operateurs = []
147 self.cr.purge() # on vide le cr
148 self.init_cr() # on initialise le cr
149 self.interprete_formule()
150 return self.cr.estvide()
152 def interprete_formule(self):
154 Réalise l'interprétation du corps de la formule
156 texte = self.t_formule[3]
157 if not texte : return
158 if type(texte) != types.ListType:
160 for text_arg in texte:
161 text_arg = string.replace(text_arg,'\n','')
163 text_arg = string.replace(text_arg,' ','')
165 self.l_operateurs.append(self.split_operateurs(text_arg))
166 except InterpreteurException,e:
167 self.cr.fatal(e.__str__())
169 def modify_listes(self):
171 Modifie la liste des constantes en lui ajoutant le nom des paramètres
172 de la fonction à interpréter
174 args = self.t_formule[2]
175 # l'interpréteur de formule sert aussi à évaluer les EVAL
176 # dans ce cas il n'y a pas d'arguments puisque pas de fonction ...
178 args = args[1:-1] # on enlève les parenthèses ouvrante et fermante
179 l_args = string.split(args,',')
181 typ,nom = string.split(arg,':')
182 nom = string.strip(nom)
183 self.l_constantes.append(nom)
184 # on considère que les fonctions unaires de base sont toutes à un seul argument :
186 self.d_fonctions_unaires = {}
187 for fct in self.l_fonctions_unaires:
188 self.d_fonctions_unaires[fct]=1
189 # on ajoute les constantes externes
190 for cte in self.new_constantes:
191 self.l_constantes.append(cte)
192 # on ajoute les fonctions unaires externes au dictionnaire des fonctions unaires
193 for new_fonc in self.new_fonctions_unaires:
194 self.d_fonctions_unaires[new_fonc[0]] = self.get_nb_args(new_fonc)
195 #self.d_fonctions_unaires.update(self.new_fonctions_unaires)
196 self.l_fonctions_unaires = self.d_fonctions_unaires.keys()
198 def ordonne_listes(self):
200 Ordonne les listes de fonctions unaires et binaires
202 self.l_fonctions_binaires.sort(cmp_function)
203 self.l_fonctions_unaires.sort(cmp_function)
204 self.l_constantes.sort(cmp_function)
207 def split_operateurs(self,texte):
209 Splite le texte passé en argument en opérateurs plus élémentaires.
210 N'analyse pas l'intérieur des opérateurs (ne fait qu'une passe)
213 texte = string.strip(texte)
214 # on recherche un nombre en début de texte
216 oper,reste = self.cherche_nombre(texte)
217 except InterpreteurException,e:
218 raise InterpreteurException,e.__str__()
220 # on recherche une constante en début de texte
222 oper,reste = self.cherche_constante(texte)
223 except InterpreteurException,e:
224 raise InterpreteurException,e.__str__()
226 # on recherche une expression entre parenthèses...
228 oper,reste = self.cherche_expression_entre_parentheses(texte)
229 except InterpreteurException,e:
230 raise InterpreteurException,e.__str__()
232 # on recherche le début d'un opérateur unaire en début de texte
234 oper,reste = self.cherche_operateur_unaire(texte)
235 except InterpreteurException,e:
236 raise InterpreteurException,e.__str__()
238 type_objet,nom_objet = self.get_type(texte)
239 if type_objet == 'constante':
240 raise InterpreteurException, "Constante %s inconnue" %nom_objet
241 elif type_objet == 'fonction':
242 raise InterpreteurException, "Fonction %s inconnue dans %s" %(nom_objet,texte)
244 raise InterpreteurException, "Impossible d'interpréter : %s" %texte
245 # on a trouvé un opérateur (nombre, constante ou unaire)
246 # il faut encore vérifier que l'on est en fin de texte ou qu'il est bien suivi
247 # d'un opérateur binaire
248 l_operateurs.append(oper)
250 texte = string.strip(reste)
251 oper,reste = self.cherche_operateur_binaire(texte)
253 # on a un reste et pas d'opérateur binaire --> erreur
254 raise InterpreteurException,"L'opérateur %s doit être suivi d'un opérateur binaire" %l_operateurs[-1]
256 # on a bien trouvé un opérateur binaire:
257 l_operateurs.append(oper)
258 # il faut recommencer l'analyse du reste par split_operateurs ...
260 l_op = self.split_operateurs(reste)
261 except InterpreteurException,e:
262 raise InterpreteurException,e.__str__()
263 l_operateurs.extend(l_op)
266 # on a fini d'analyser texte
269 def cherche_nombre(self,texte):
271 Cherche un nombre en début de texte
272 Retourne ce nombre et le reste ou None et le texte initial
273 Peut lever une InterpreteurException dans le cas où le nombre n'est pas valide
275 texte = string.strip(texte)
276 m = pat_number_complet.match(texte)
278 # on a trouvé un nombre avec exposant
279 l_groups = m.groups()
287 return nombre,l_groups[4]
289 m = pat_number.match(texte)
291 # on a trouvé un nombre sans exposant
292 l_groups = m.groups()
298 # il faut vérifier si ce nombre n'est pas suivi d'un exposant incomplet ...
299 reste = string.strip(l_groups[3])
301 return nombre,l_groups[3]
302 if reste[0] in ('e','E','d','D') :
303 raise InterpreteurException,"La syntaxe de l'exposant de %s est erronée " %nb
305 return nombre,l_groups[3]
307 # on n'a pas trouvé de nombre
310 def cherche_constante_old(self,texte):
312 Recherche une constante en début de texte parmi la liste des constantes.
313 Retourne le texte représentant la constante et le reste du texte ou
314 Retourne None,texte si aucune constante trouvée
317 texte = string.strip(texte)
318 for cte in self.l_constantes:
319 index = string.find(texte,cte)
320 #if index == 0 : print 'on a trouvé %s dans %s en %d' %(cte,texte,index)
323 zz,reste = string.split(texte,cte,1)
328 # aucune constante trouvée
331 def cherche_constante(self,texte):
333 Recherche une constante en début de texte parmi la liste des constantes.
334 Retourne le texte représentant la constante et le reste du texte ou
335 Retourne None,texte si aucune constante trouvée
338 texte = string.strip(texte)
339 m = pat_constante.match(texte)
341 # on a trouvé un identificateur en début de texte
342 l_groups = m.groups()
344 identificateur = string.strip(l_groups[1])
346 # il faut vérifier qu'il ne s'agit pas d'un appel à une fonction
349 # --> appel de fonction
351 # il faut encore vérifier qu'elle est bien dans la liste des constantes...
352 if identificateur not in self.l_constantes :
353 raise InterpreteurException,"La constante %s est inconnue dans %s" %(identificateur,texte)
355 return sgn+identificateur,reste
357 # aucune constante trouvée
360 def cherche_args(self,texte):
362 Cherche au début de texte une liste d'arguments entre parenthèses
372 # on a atteint la fin de texte sans avoir trouvé la parenthèse fermante --> erreur
373 raise InterpreteurException,"Manque parenthèse fermante dans %s" %texte
378 if (n+1 < len(texte)):
379 return texte[0:n+1],texte[n+1:]
381 # on a fini d'analyser le texte : reste = None
384 def cherche_operateur_unaire_old(self,texte):
386 Cherche dans texte un operateur unaire
389 texte = string.strip(texte)
390 for oper in self.l_fonctions_unaires:
391 index = string.find(texte,oper)
394 zz,reste = string.split(texte,oper,1)
397 #print 'on a trouvé :',txt
401 args,reste = self.cherche_args(texte)
402 except InterpreteurException,e:
403 raise InterpreteurException,e.__str__()
405 # opérateur unaire sans arguments
406 raise InterpreteurException,'opérateur unaire %s sans arguments' %operateur
408 #operateur = operateur+args
409 args = self.split_args(txt,args,self.d_fonctions_unaires[operateur])
410 formule_operateur = (txt,'',self.t_formule[2],args)
411 operateur = Interpreteur_Formule(formule = formule_operateur,
412 constantes = self.new_constantes,
413 fonctions_unaires = self.new_fonctions_unaires,
415 operateur.interprete_formule()
417 return operateur,reste
419 # aucun opérateur unaire trouvé
422 def cherche_operateur_unaire(self,texte):
424 Cherche dans texte un operateur unaire
427 texte = string.strip(texte)
428 m = pat_constante.match(texte)
430 # on a trouvé un identificateur en début de texte
431 # il faut encore vérifier que l'on a bien à faire à un appel de fonction ...
432 l_groups = m.groups()
434 identificateur = string.strip(l_groups[1])
437 args,reste = self.cherche_args(reste)
438 except InterpreteurException,e:
439 raise InterpreteurException,e.__str__()
441 # opérateur unaire sans arguments
442 # en principe on ne doit jamais être dans ce cas car il est déjà trappé par cherche_constante ...
443 raise InterpreteurException,'Fonction %s sans arguments !' %identificateur
445 # il faut encore vérifier que l'on a bien à faire à une fonction connue
446 if identificateur not in self.l_fonctions_unaires:
447 raise InterpreteurException,'Fonction %s inconnue dans %s !' %(identificateur,texte)
448 args = self.split_args(identificateur,args,self.d_fonctions_unaires[identificateur])
449 formule_operateur = (sgn+identificateur,'',self.t_formule[2],args)
450 operateur = Interpreteur_Formule(formule = formule_operateur,
451 constantes = self.new_constantes,
452 fonctions = self.new_fonctions_unaires,
454 operateur.interprete_formule()
456 return operateur,reste
457 elif texte[0] == '-':
458 # Il faut pouvoir trapper les expressions du type exp(-(x+1)) ...
460 args,reste = self.cherche_args(texte[1:])
461 except InterpreteurException,e:
462 raise InterpreteurException,e.__str__()
464 # Il ne s'agit pas de '-' comme opérateur unaire --> on retourne None
468 args = self.split_args(identificateur,args,self.d_fonctions_unaires[identificateur])
469 formule_operateur = (identificateur,'',self.t_formule[2],args)
470 operateur = Interpreteur_Formule(formule = formule_operateur,
471 constantes = self.new_constantes,
472 fonctions = self.new_fonctions_unaires,
474 operateur.interprete_formule()
476 return operateur,reste
480 def cherche_operateur_binaire(self,texte):
482 Cherche dans texte un operateur unaire
485 texte = string.strip(texte)
486 for oper in self.l_fonctions_binaires:
487 index = string.find(texte,oper)
488 #if index != -1 : print 'on a trouvé %s dans %s en %d' %(oper,texte,index)
491 zz,reste = string.split(texte,oper,1)
496 # aucun opérateur unaire trouvé
499 def cherche_expression_entre_parentheses(self,texte):
501 Cherche en début de texte une expression entre parentheses
503 args,reste = self.cherche_args(string.strip(texte))
507 # on a trouvé une expression entre parenthèses en début de texte
508 # --> on retourne un objet Interpreteur_Formule
509 formule_operateur = ('','',self.t_formule[2],args[1:-1])
510 operateur = Interpreteur_Formule(formule = formule_operateur,
511 constantes = self.new_constantes,
512 fonctions = self.new_fonctions_unaires,
514 operateur.interprete_formule()
516 return operateur,reste
518 def split_args(self,nom_fonction,args,nb_args):
520 Tente de partager args en nb_args éléments
521 Retourne une liste de chaînes de caractères (liste de longueur nb_args)
523 args = args[1:-1] # on enlève les parenthèses ouvrante et fermante
524 if nb_args == 1 : return args
525 l_args = string.split(args,',')
526 if len(l_args) != nb_args:
527 raise InterpreteurException,"La fonction %s requiert %d arguments : %d fourni(s)" %(nom_fonction,nb_args,len(l_args))
531 def get_type(self,texte):
533 Retourne le type de l'objet défini dans texte, à savoir:
539 texte = string.strip(texte)
541 return 'constante',texte
544 nom_oper,args = string.split(texte,'(',1)
545 return 'fonction',nom_oper
547 def get_nb_args(self,formule):
549 Retourne le nombre d'arguments dans la définition de formule (sous forme de tuple)
551 args = formule[2][1:-1] # on enlève les parenthèses ouvrante et fermante
552 l_args = string.split(args,',')
555 if __name__ == '__main__':
556 constantes = ['FREQ3','AMOR1']
557 fonctions_unaires=[('ACC','REEL','(REEL:x)','''bidon'''),]
558 f1 = ('f1','REEL','(REEL:x)','''SIN(x)+3*x''')
559 f2 = ('f2','REEL','(REEL:x)','''ATAN(x+3)+3*x''')
560 f3 = ('f3','REEL','(REEL:INST)','''ACC(INST,FREQ3,AMOR1)''')
561 f4 = ('f4','REEL','(REEL:INST)','''ACC(INST,FREQ2,AMOR1)''')
562 f5 = ('f5','REEL','(REEL:INST,REEL:Y)','''ACC(INST,FREQ3,AMOR1)+Y*INST''')
563 f6 = ('f6','REEL','(REEL:x)','''(x+ 3)/ 35.698''')
564 f7 = ('f7','REEL','(REEL:x)','''(x+ 3)/ 35.698E-10''')
565 f8 = ('f8','REEL','(REEL:x)','''(x+ 3)/ 35.698E''')
566 f9 = ('f9','REEL','(REEL:INSTA,REEl:INSTB)','''2.*SIN((PI/4)+((INSTA-INSTB)/2.))* COS((PI/4)-((INSTA+INSTB)/2.))''')
567 f10 = ('f10','REEL','(REEL:X)','''EXP(-(X+1))''')
568 for formule in (f1,f2,f3,f4,f5,f6,f7,f8,f9,f10):
569 i = Interpreteur_Formule(formule = formule,
570 constantes = constantes,
571 fonctions = fonctions_unaires)
573 print '\nformule %s = %s' %(str(formule),txt)
575 print "\n\tPas d'erreur !"