Salome HOME
CCAR : mise a jour avec la version 7.3.23 du Noyau superviseur Aster
[tools/eficas.git] / Noyau / N_MACRO_ETAPE.py
1 #@ MODIF N_MACRO_ETAPE Noyau  DATE 14/09/2004   AUTEUR MCOURTOI M.COURTOIS 
2 # -*- coding: iso-8859-1 -*-
3 #            CONFIGURATION MANAGEMENT OF EDF VERSION
4 # ======================================================================
5 # COPYRIGHT (C) 1991 - 2002  EDF R&D                  WWW.CODE-ASTER.ORG
6 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
7 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
8 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR   
9 # (AT YOUR OPTION) ANY LATER VERSION.                                 
10 #
11 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT 
12 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF          
13 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU    
14 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.                            
15 #
16 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE   
17 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,       
18 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.      
19 #                                                                       
20 #                                                                       
21 # ======================================================================
22
23
24 """ 
25     Ce module contient la classe MACRO_ETAPE qui sert à vérifier et à exécuter
26     une commande
27 """
28
29 # Modules Python
30 import types,sys,string
31 import traceback
32
33 # Modules EFICAS
34 import N_MCCOMPO
35 import N_ETAPE
36 from N_Exception import AsException
37 import N_utils
38 from N_utils import AsType
39
40 class MACRO_ETAPE(N_ETAPE.ETAPE):
41    """
42
43    """
44    nature = "COMMANDE"
45    def __init__(self,oper=None,reuse=None,args={}):
46       """
47          Attributs :
48
49           - definition : objet portant les attributs de définition d'une étape 
50                          de type macro-commande. Il est initialisé par 
51                           l'argument oper.
52
53           - reuse : indique le concept d'entrée réutilisé. Il se trouvera donc
54                     en sortie si les conditions d'exécution de l'opérateur 
55                     l'autorise
56
57           - valeur : arguments d'entrée de type mot-clé=valeur. Initialisé 
58                      avec l'argument args.
59
60       """
61       self.definition=oper
62       self.reuse=reuse
63       self.valeur=args
64       self.nettoiargs()
65       self.parent=CONTEXT.get_current_step()
66       self.etape = self
67       self.nom=oper.nom
68       self.idracine=oper.label
69       self.appel=N_utils.callee_where()
70       self.mc_globaux={}
71       self.g_context={}
72       # Contexte courant
73       self.current_context={}
74       self.index_etape_courante=0
75       self.etapes=[]
76       self.sds=[]
77       #  Dans le cas d'une macro écrite en Python, l'attribut Outputs est un 
78       #  dictionnaire qui contient les concepts produits de sortie 
79       #  (nom : ASSD) déclarés dans la fonction sd_prod
80       self.Outputs={}
81       self.sd=None
82       self.actif=1
83       self.sdprods=[]
84       self.make_register()
85
86    def make_register(self):
87       """
88          Initialise les attributs jdc, id, niveau et réalise les enregistrements
89          nécessaires
90       """
91       if self.parent :
92          self.jdc = self.parent.get_jdc_root()
93          self.id=self.parent.register(self)
94          self.niveau=None
95       else:
96          self.jdc = self.parent =None
97          self.id=None
98          self.niveau=None
99
100    def Build_sd(self,nom):
101       """
102          Construit le concept produit de l'opérateur. Deux cas 
103          peuvent se présenter :
104         
105          - le parent n'est pas défini. Dans ce cas, l'étape prend en charge 
106            la création et le nommage du concept.
107
108          - le parent est défini. Dans ce cas, l'étape demande au parent la 
109            création et le nommage du concept.
110
111       """
112       if not self.isactif():return
113       self.sdnom=nom
114       try:
115          # On positionne la macro self en tant que current_step pour que les 
116          # étapes créées lors de l'appel à sd_prod et à op_init aient la macro
117          #  comme parent 
118          self.set_current_step()
119          if self.parent:
120             sd= self.parent.create_sdprod(self,nom)
121             if type(self.definition.op_init) == types.FunctionType: 
122                apply(self.definition.op_init,(self,self.parent.g_context))
123          else:
124             sd=self.get_sd_prod()
125             if sd != None and self.reuse == None:
126                # On ne nomme le concept que dans le cas de non reutilisation 
127                # d un concept
128                sd.nom=nom
129          self.reset_current_step()
130       except AsException,e:
131          self.reset_current_step()
132          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
133                               'fichier : ',self.appel[1],e)
134       except (EOFError,self.jdc.UserError):
135          # Le retablissement du step courant n'est pas strictement necessaire. On le fait pour des raisons de coherence
136          self.reset_current_step()
137          raise
138       except :
139          self.reset_current_step()
140          l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
141          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
142                            'fichier : ',self.appel[1]+'\n',
143                             string.join(l))
144
145       self.Execute()
146       return sd
147
148    def get_sd_prod(self):
149       """
150         Retourne le concept résultat d'une macro étape
151         La difference avec une etape ou une proc-etape tient a ce que
152          le concept produit peut exister ou pas
153         Si sd_prod == None le concept produit n existe pas on retourne None
154         Deux cas :
155          cas 1 : sd_prod  n'est pas une fonction
156                  il s'agit d'une sous classe de ASSD
157                  on construit le sd à partir de cette classe
158                  et on le retourne
159          cas 2 : sd_prod est une fonction
160                   on l'évalue avec les mots-clés de l'étape (mc_liste)
161                  on construit le sd à partir de la classe obtenue
162                  et on le retourne
163       """
164       sd_prod=self.definition.sd_prod
165       self.typret=None
166       if type(self.definition.sd_prod) == types.FunctionType:
167         d=self.cree_dict_valeurs(self.mc_liste)
168         try:
169           # la sd_prod d'une macro a l'objet macro_etape lui meme en premier argument
170           # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
171           # les concepts produits dans self.sdprods, il faut le mettre à zéro avant de l'appeler
172           self.sdprods=[]
173           sd_prod= apply(sd_prod,(self,),d)
174         except (EOFError,self.jdc.UserError):
175           raise
176         except:
177           if CONTEXT.debug: traceback.print_exc()
178           l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
179           raise AsException("impossible d affecter un type au resultat\n",string.join(l[2:]))
180
181       # on teste maintenant si la SD est réutilisée ou s'il faut la créer
182       if self.definition.reentrant != 'n' and self.reuse:
183         # Le concept produit est specifie reutilise (reuse=xxx). C'est une erreur mais non fatale.
184         # Elle sera traitee ulterieurement.
185         self.sd=self.reuse
186       else:
187         if sd_prod == None:
188           self.sd=None
189         else:
190           self.sd= sd_prod(etape=self)
191           self.typret=sd_prod
192           # Si la commande est obligatoirement reentrante et reuse n'a pas ete specifie, c'est une erreur. 
193           # On ne fait rien ici. L'erreur sera traitee par la suite. 
194       return self.sd
195
196    def get_type_produit(self,force=0):
197       """
198            Retourne le type du concept résultat de l'étape et eventuellement type
199             les concepts produits "à droite" du signe égal (en entrée)
200            Deux cas :
201             cas 1 : sd_prod de oper n'est pas une fonction
202                     il s'agit d'une sous classe de ASSD
203                     on retourne le nom de la classe
204             cas 2 : il s'agit d'une fonction
205                     on l'évalue avec les mots-clés de l'étape (mc_liste)
206                     et on retourne son résultat
207       """
208       if not force and hasattr(self,'typret'): return self.typret
209       if type(self.definition.sd_prod) == types.FunctionType:
210         d=self.cree_dict_valeurs(self.mc_liste)
211         try:
212           # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
213           # les concepts produits dans self.sdprods, il faut le mettre à zéro
214           self.sdprods=[]
215           sd_prod= apply(self.definition.sd_prod,(self,),d)
216         except:
217           #traceback.print_exc()
218           return None
219       else:
220         sd_prod=self.definition.sd_prod
221       return sd_prod
222
223    def get_contexte_avant(self,etape):
224       """
225           Retourne le dictionnaire des concepts connus avant etape
226           pour les commandes internes a la macro
227           On tient compte des commandes qui modifient le contexte
228           comme DETRUIRE ou les macros
229       """
230       # L'étape courante pour laquelle le contexte a été calculé est 
231       # mémorisée dans self.index_etape_courante
232       # Si on insère des commandes (par ex, dans EFICAS), il faut
233       # préalablement remettre ce pointeur à 0
234       if etape:
235          index_etape=self.etapes.index(etape)
236       else:
237          index_etape=len(self.etapes)
238
239       if index_etape >= self.index_etape_courante:
240          # On calcule le contexte en partant du contexte existant
241          d=self.current_context
242          liste_etapes=self.etapes[self.index_etape_courante:index_etape]
243       else:
244          d=self.current_context={}
245          liste_etapes=self.etapes
246
247       for e in liste_etapes:
248         if e is etape:
249            break
250         if e.isactif():
251            e.update_context(d)
252       self.index_etape_courante=index_etape
253       return d
254
255    def supprime(self):
256       """
257          Méthode qui supprime toutes les références arrières afin que 
258          l'objet puisse etre correctement détruit par le garbage collector
259       """
260       N_MCCOMPO.MCCOMPO.supprime(self)
261       self.jdc=None
262       self.appel=None
263       if self.sd : self.sd.supprime()
264       for concept in self.sdprods:
265          concept.supprime()
266       for etape in self.etapes:
267          etape.supprime()
268
269    def type_sdprod(self,co,t):
270       """
271            Cette methode a pour fonction de typer le concept co avec le type t
272             dans les conditions suivantes
273             1- co est un concept produit de self
274             2- co est un concept libre : on le type et on l attribue à self
275            Elle enregistre egalement les concepts produits (on fait l hypothese
276             que la liste sdprods a été correctement initialisee, vide probablement)
277       """
278       if not hasattr(co,'etape'):
279          # Le concept vaut None probablement. On ignore l'appel
280          return
281
282       if co.etape == None:
283          # le concept est libre
284          co.etape=self
285          co.__class__ = t
286          self.sdprods.append(co)
287       elif co.etape== self:
288          # le concept est produit par self
289          co.__class__ = t
290          self.sdprods.append(co)
291       elif co.etape== self.parent:
292          # le concept est produit par la macro superieure
293          # on transfere la propriete
294          # On verifie que le type du concept existant co.__class__ est un sur type de celui attendu
295          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
296          if not issubclass(t,co.__class__):
297             raise AsException("Le type du concept produit %s devrait etre une sur classe de %s" %(co.__class__,t))
298          co.etape=self
299          co.__class__ = t
300          self.sdprods.append(co)
301       elif self.issubstep(co.etape):
302          # Le concept est propriété d'une sous etape de self. Il doit etre considere
303          # comme produit par la macro => ajout dans self.sdprods
304          self.sdprods.append(co)
305       else:
306          # le concept est produit par une autre étape
307          return
308
309    def issubstep(self,etape):
310       """ 
311           Cette methode retourne un entier indiquant si etape est une
312           sous etape de la macro self ou non
313           1 = oui
314           0 = non
315       """
316       if etape in self.etapes:return 1
317       for etap in self.etapes:
318         if etap.issubstep(etape):return 1
319       return 0
320
321    def register(self,etape):
322       """ 
323           Enregistrement de etape dans le contexte de la macro : liste etapes 
324           et demande d enregistrement global aupres du JDC
325       """
326       self.etapes.append(etape)
327       idetape=self.jdc.g_register(etape)
328       return idetape
329
330    def reg_sd(self,sd):
331       """ 
332            Methode appelee dans l __init__ d un ASSD a sa creation pour
333            s enregistrer (reserve aux ASSD créés au sein d'une MACRO)
334       """
335       self.sds.append(sd)
336       return self.jdc.o_register(sd)
337
338    def create_sdprod(self,etape,nomsd):
339       """ 
340           Intention : Cette methode doit fabriquer le concept produit retourne
341                   par l'etape etape et le nommer.
342                   Elle est appelée à l'initiative de l'etape
343                   pendant le processus de construction de cette etape : methode __call__
344                   de la classe CMD (OPER ou MACRO)
345                   Ce travail est réalisé par le contexte supérieur (etape.parent)
346                   car dans certains cas, le concept ne doit pas etre fabriqué mais
347                   l'etape doit simplement utiliser un concept préexistant.
348                   Cas 1 : etape.reuse != None : le concept est réutilisé
349                   Cas 2 : l'étape appartient à une macro qui a déclaré un concept
350                           de sortie qui doit etre produit par cette etape.
351       """
352       if self.Outputs.has_key(nomsd):
353          # Il s'agit d'un concept de sortie de la macro. Il ne faut pas le créer
354          # Il faut quand meme appeler la fonction sd_prod si elle existe.
355          # get_type_produit le fait et donne le type attendu par la commande pour verification ultérieure.
356          sdprod=etape.get_type_produit()
357          sd=self.Outputs[nomsd]
358          # On verifie que le type du concept existant sd.__class__ est un sur type de celui attendu
359          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
360          if not issubclass(sdprod,sd.__class__):
361             raise AsException("Le type du concept produit %s devrait etre une sur classe de %s" %(sd.__class__,sdprod))
362          # La propriete du concept est transferee a l'etape avec le type attendu par l'étape
363          etape.sd=sd
364          sd.etape=etape
365          # On donne au concept le type produit par la sous commande.
366          # Le principe est le suivant : apres avoir verifie que le type deduit par la sous commande
367          # est bien coherent avec celui initialement affecte par la macro (voir ci dessus)
368          # on affecte au concept ce type car il peut etre plus precis (derive, en general)
369          sd.__class__=sdprod
370          # On force également le nom stocké dans l'attribut sdnom : on lui donne le nom 
371          # du concept associé à nomsd
372          etape.sdnom=sd.nom
373       elif etape.definition.reentrant != 'n' and etape.reuse != None:
374          # On est dans le cas d'une commande avec reutilisation d'un concept existant
375          # get_sd_prod fait le necessaire : verifications, associations, etc. mais ne cree 
376          # pas un nouveau concept. Il retourne le concept reutilise
377          sd= etape.get_sd_prod()
378          # Dans le cas d'un concept nomme automatiquement : _xxx, __xxx,
379          # On force le nom stocke dans l'attribut sdnom  de l'objet etape : on lui donne le nom 
380          # du concept  reutilise (sd ou etape.reuse c'est pareil)
381          # Ceci est indispensable pour eviter des erreurs lors des verifications des macros
382          # En effet une commande avec reutilisation d'un concept verifie que le nom de 
383          # la variable a gauche du signe = est le meme que celui du concept reutilise.
384          # Lorsqu'une telle commande apparait dans une macro, on supprime cette verification.
385          if (etape.sdnom == '' or etape.sdnom[0] == '_'):
386             etape.sdnom=sd.nom
387       else:
388          # On est dans le cas de la creation d'un nouveau concept
389          sd= etape.get_sd_prod()
390          if sd != None :
391             self.NommerSdprod(sd,nomsd)
392       return sd
393
394    def NommerSdprod(self,sd,sdnom,restrict='non'):
395       """ 
396           Cette methode est appelee par les etapes internes de la macro
397           La macro appelle le JDC pour valider le nommage
398           On considere que l espace de nom est unique et géré par le JDC
399           Si le nom est deja utilise, l appel leve une exception
400           Si restrict=='non', on insere le concept dans le contexte de la macro
401           Si restrict=='oui', on n'insere pas le concept dans le contexte de la macro
402       """
403       # Normalement, lorsqu'on appelle cette methode, on ne veut nommer que des concepts nouvellement crees.
404       # Le filtrage sur les concepts a creer ou a ne pas creer est fait dans la methode
405       # create_sdprod. La seule chose a verifier apres conversion eventuelle du nom
406       # est de verifier que le nom n'est pas deja attribue. Ceci est fait en delegant
407       # au JDC par l'intermediaire du parent.
408
409       #XXX attention inconsistence : prefix et gcncon ne sont pas 
410       # définis dans le package Noyau. La methode NommerSdprod pour
411       # les macros devrait peut etre etre déplacée dans Build ???
412
413       if CONTEXT.debug : print "MACRO.NommerSdprod: ",sd,sdnom
414
415       if hasattr(self,'prefix'):
416         # Dans le cas de l'include_materiau on ajoute un prefixe au nom du concept
417         if sdnom != self.prefix:sdnom=self.prefix+sdnom
418
419       if self.Outputs.has_key(sdnom):
420         # Il s'agit d'un concept de sortie de la macro produit par une sous commande
421         sdnom=self.Outputs[sdnom].nom
422       elif sdnom != '' and sdnom[0] == '_':
423         # Si le nom du concept commence par le caractere _ on lui attribue
424         # un identificateur JEVEUX construit par gcncon et respectant
425         # la regle gcncon legerement adaptee ici
426         # nom commencant par __ : il s'agit de concepts qui seront detruits
427         # nom commencant par _ : il s'agit de concepts intermediaires qui seront gardes
428         # ATTENTION : il faut traiter différemment les concepts dont le nom
429         # commence par _ mais qui sont des concepts nommés automatiquement par
430         # une éventuelle sous macro.
431         # Le test suivant n'est pas tres rigoureux mais permet de fonctionner pour le moment (a améliorer)
432         if sdnom[1] in string.digits:
433           # Ce concept provient probablement d'une macro appelee par self
434           pass
435         elif sdnom[1] == '_':
436           sdnom=self.gcncon('.')
437         else:
438           sdnom=self.gcncon('_')
439       else:
440         # On est dans le cas d'un nom de concept global. 
441         pass
442
443       if restrict == 'non':
444          # On demande le nommage au parent mais sans ajout du concept dans le contexte du parent
445          # car on va l'ajouter dans le contexte de la macro
446          self.parent.NommerSdprod(sd,sdnom,restrict='oui')
447          # On ajoute dans le contexte de la macro les concepts nommes
448          # Ceci est indispensable pour les CO (macro) dans un INCLUDE
449          self.g_context[sdnom]=sd
450       else:
451          # La demande de nommage vient probablement d'une macro qui a mis
452          # le concept dans son contexte. On ne traite plus que le nommage (restrict="oui")
453          self.parent.NommerSdprod(sd,sdnom,restrict='oui')
454
455    def delete_concept_after_etape(self,etape,sd):
456       """
457           Met à jour les étapes de la MACRO  qui sont après etape suite à
458           la disparition du concept sd
459       """
460       # Cette methode est définie dans le noyau mais ne sert que pendant la phase de creation
461       # des etapes et des concepts. Il n'y a aucun traitement particulier à réaliser
462       # Dans d'autres conditions, il faudrait surcharger cette méthode.
463       return
464
465    def accept(self,visitor):
466       """
467          Cette methode permet de parcourir l'arborescence des objets
468          en utilisant le pattern VISITEUR
469       """
470       visitor.visitMACRO_ETAPE(self)
471
472    def update_context(self,d):
473       """
474          Met à jour le contexte contenu dans le dictionnaire d
475          Une MACRO_ETAPE peut ajouter plusieurs concepts dans le contexte
476          Une fonction enregistree dans op_init peut egalement modifier le contexte
477       """
478       if type(self.definition.op_init) == types.FunctionType:
479         apply(self.definition.op_init,(self,d))
480       if self.sd != None:d[self.sd.nom]=self.sd
481       for co in self.sdprods:
482         d[co.nom]=co
483
484    def make_include(self,unite=None):
485       """
486           Inclut un fichier dont l'unite logique est unite
487       """
488       if not unite : return
489       f,text=self.get_file(unite=unite,fic_origine=self.parent.nom)
490       self.fichier_init = f
491       if f == None:return
492       self.make_contexte(f,text)
493
494    def make_poursuite(self):
495       """
496           Inclut un fichier poursuite
497       """
498       try:
499          f,text=self.get_file(fic_origine=self.parent.nom)
500       except:
501          raise AsException("Impossible d'ouvrir la base pour une poursuite")
502       self.fichier_init=f
503       if f == None:return
504       self.make_contexte(f,text)
505
506    def make_contexte(self,f,text):
507       """
508           Interprete le texte fourni (text) issu du fichier f
509           dans le contexte du parent.
510           Cette methode est utile pour le fonctionnement des
511           INCLUDE
512       """
513       # on execute le texte fourni dans le contexte forme par
514       # le contexte de l etape pere (global au sens Python)
515       # et le contexte de l etape (local au sens Python)
516       code=compile(text,f,'exec')
517       d={}
518       self.g_context = d
519       self.contexte_fichier_init = d
520       globs=self.parent.get_global_contexte()
521       exec code in globs,d
522
523    def get_global_contexte(self):
524       """
525           Cette methode retourne le contexte global fourni
526           par le parent(self) a une etape fille (l'appelant) pour
527           realiser des evaluations de texte Python (INCLUDE,...)
528       """
529       # Le contexte global est forme par concatenation du contexte
530       # du parent de self et de celui de l'etape elle meme (self)
531       d=self.parent.get_global_contexte()
532       d.update(self.g_context)
533       return d
534
535    def copy(self):
536       """ Méthode qui retourne une copie de self non enregistrée auprès du JDC
537           et sans sd
538           On surcharge la methode de ETAPE pour exprimer que les concepts crees
539           par la MACRO d'origine ne sont pas crees par la copie mais eventuellement
540           seulement utilises
541       """
542       etape=N_ETAPE.ETAPE.copy(self)
543       etape.sdprods=[]
544       return etape
545
546    def copy_intern(self,etape):
547       """ Cette méthode effectue la recopie des etapes internes d'une macro 
548           passée en argument (etape)
549       """
550       self.etapes=[]
551       for etp in etape.etapes:
552           new_etp=etp.copy()
553           new_etp.copy_reuse(etp)
554           new_etp.copy_sdnom(etp)
555           new_etp.reparent(self)
556           if etp.sd:
557              new_sd = etp.sd.__class__(etape=new_etp)
558              new_etp.sd = new_sd
559              if etp.reuse:
560                 new_sd.nom = etp.sd.nom
561              else:
562                 self.NommerSdprod(new_sd,etp.sd.nom)
563           new_etp.copy_intern(etp)
564           self.etapes.append(new_etp)
565
566
567
568
569
570
571