Salome HOME
Version initiale de EFICAS 1.2
[tools/eficas.git] / Noyau / N_MACRO_ETAPE.py
1 """ 
2     Ce module contient la classe MACRO_ETAPE qui sert à vérifier et à exécuter
3     une commande
4 """
5
6 # Modules Python
7 import types,sys,string
8 import traceback
9
10 # Modules EFICAS
11 import N_MCCOMPO
12 import N_ETAPE
13 from N_Exception import AsException
14 import N_utils
15 from N_utils import AsType
16
17 class MACRO_ETAPE(N_ETAPE.ETAPE):
18    """
19
20    """
21    nature = "COMMANDE"
22    def __init__(self,oper=None,reuse=None,args={}):
23       """
24          Attributs :
25
26           - definition : objet portant les attributs de définition d'une étape 
27                          de type macro-commande. Il est initialisé par 
28                           l'argument oper.
29
30           - reuse : indique le concept d'entrée réutilisé. Il se trouvera donc
31                     en sortie si les conditions d'exécution de l'opérateur 
32                     l'autorise
33
34           - valeur : arguments d'entrée de type mot-clé=valeur. Initialisé 
35                      avec l'argument args.
36
37       """
38       self.definition=oper
39       self.reuse=reuse
40       self.valeur=args
41       self.nettoiargs()
42       self.parent=CONTEXT.get_current_step()
43       self.etape = self
44       self.nom=oper.nom
45       self.idracine=oper.label
46       self.appel=N_utils.callee_where()
47       self.mc_globaux={}
48       self.g_context={}
49       # Contexte courant
50       self.current_context={}
51       self.index_etape_courante=0
52       self.etapes=[]
53       self.sds=[]
54       #  Dans le cas d'une macro écrite en Python, l'attribut Outputs est un 
55       #  dictionnaire qui contient les concepts produits de sortie 
56       #  (nom : ASSD) déclarés dans la fonction sd_prod
57       self.Outputs={}
58       self.sd=None
59       self.actif=1
60       self.sdprods=[]
61       self.make_register()
62
63    def make_register(self):
64       """
65          Initialise les attributs jdc, id, niveau et réalise les enregistrements
66          nécessaires
67       """
68       if self.parent :
69          self.jdc = self.parent.get_jdc_root()
70          self.id=self.parent.register(self)
71          self.niveau=None
72       else:
73          self.jdc = self.parent =None
74          self.id=None
75          self.niveau=None
76
77    def Build_sd(self,nom):
78       """
79          Construit le concept produit de l'opérateur. Deux cas 
80          peuvent se présenter :
81         
82          - le parent n'est pas défini. Dans ce cas, l'étape prend en charge 
83            la création et le nommage du concept.
84
85          - le parent est défini. Dans ce cas, l'étape demande au parent la 
86            création et le nommage du concept.
87
88       """
89       if not self.isactif():return
90       try:
91          # On positionne la macro self en tant que current_step pour que les 
92          # étapes créées lors de l'appel à sd_prod et à op_init aient la macro
93          #  comme parent 
94          self.set_current_step()
95          if self.parent:
96             sd= self.parent.create_sdprod(self,nom)
97             if type(self.definition.op_init) == types.FunctionType: 
98                apply(self.definition.op_init,(self,self.parent.g_context))
99          else:
100             sd=self.get_sd_prod()
101             if sd != None and self.reuse == None:
102                # On ne nomme le concept que dans le cas de non reutilisation 
103                # d un concept
104                sd.nom=nom
105          self.reset_current_step()
106          if self.jdc and self.jdc.par_lot == "NON" :
107             self.Execute()
108          return sd
109       except AsException,e:
110          self.reset_current_step()
111          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
112                               'fichier : ',self.appel[1],e)
113       except EOFError:
114          #self.reset_current_step()
115          raise
116       except :
117          self.reset_current_step()
118          l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
119          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
120                            'fichier : ',self.appel[1]+'\n',
121                             string.join(l))
122
123    def get_sd_prod(self):
124       """
125         Retourne le concept résultat d'une macro étape
126         La difference avec une etape ou une proc-etape tient a ce que
127          le concept produit peut exister ou pas
128         Si sd_prod == None le concept produit n existe pas on retourne None
129         Deux cas :
130          cas 1 : sd_prod  n'est pas une fonction
131                  il s'agit d'une sous classe de ASSD
132                  on construit le sd à partir de cette classe
133                  et on le retourne
134          cas 2 : sd_prod est une fonction
135                   on l'évalue avec les mots-clés de l'étape (mc_liste)
136                  on construit le sd à partir de la classe obtenue
137                  et on le retourne
138       """
139       sd_prod=self.definition.sd_prod
140       self.typret=None
141       if type(self.definition.sd_prod) == types.FunctionType:
142         d=self.cree_dict_valeurs(self.mc_liste)
143         try:
144           # la sd_prod d'une macro a l'objet macro_etape lui meme en premier argument
145           # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
146           # les concepts produits dans self.sdprods, il faut le mettre à zéro avant de l'appeler
147           self.sdprods=[]
148           sd_prod= apply(sd_prod,(self,),d)
149         except EOFError:
150           raise
151         except:
152           if CONTEXT.debug: traceback.print_exc()
153           l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
154           raise AsException("impossible d affecter un type au resultat\n",string.join(l[2:]))
155
156       # on teste maintenant si la SD est réutilisée ou s'il faut la créer
157       if self.reuse:
158         if AsType(self.reuse) != sd_prod:
159           raise AsException("type de concept reutilise incompatible avec type produit")
160         self.sd=self.reuse
161       else:
162         if sd_prod == None:
163           self.sd=None
164         else:
165           self.sd= sd_prod(etape=self)
166           self.typret=sd_prod
167         if self.definition.reentrant == 'o':
168           self.reuse = self.sd
169       return self.sd
170
171    def get_type_produit(self,force=0):
172       """
173            Retourne le type du concept résultat de l'étape et eventuellement type
174             les concepts produits "à droite" du signe égal (en entrée)
175            Deux cas :
176             cas 1 : sd_prod de oper n'est pas une fonction
177                     il s'agit d'une sous classe de ASSD
178                     on retourne le nom de la classe
179             cas 2 : il s'agit d'une fonction
180                     on l'évalue avec les mots-clés de l'étape (mc_liste)
181                     et on retourne son résultat
182       """
183       if not force and hasattr(self,'typret'): return self.typret
184       if type(self.definition.sd_prod) == types.FunctionType:
185         d=self.cree_dict_valeurs(self.mc_liste)
186         try:
187           # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
188           # les concepts produits dans self.sdprods, il faut le mettre à zéro
189           self.sdprods=[]
190           sd_prod= apply(self.definition.sd_prod,(self,),d)
191         except:
192           #traceback.print_exc()
193           return None
194       else:
195         sd_prod=self.definition.sd_prod
196       return sd_prod
197
198    def get_contexte_avant(self,etape):
199       """
200           Retourne le dictionnaire des concepts connus avant etape
201           pour les commandes internes a la macro
202           On tient compte des commandes qui modifient le contexte
203           comme DETRUIRE ou les macros
204       """
205       # L'étape courante pour laquelle le contexte a été calculé est 
206       # mémorisée dans self.index_etape_courante
207       # Si on insère des commandes (par ex, dans EFICAS), il faut
208       # préalablement remettre ce pointeur à 0
209       index_etape=self.etapes.index(etape)
210       if index_etape >= self.index_etape_courante:
211          # On calcule le contexte en partant du contexte existant
212          d=self.current_context
213          liste_etapes=self.etapes[self.index_etape_courante:index_etape]
214       else:
215          d=self.current_context={}
216          liste_etapes=self.etapes
217
218       for e in liste_etapes:
219         if e is etape:
220            break
221         if e.isactif():
222            e.update_context(d)
223       self.index_etape_courante=index_etape
224       return d
225
226    def supprime(self):
227       """
228          Méthode qui supprime toutes les références arrières afin que 
229          l'objet puisse être correctement détruit par le garbage collector
230       """
231       N_MCCOMPO.MCCOMPO.supprime(self)
232       self.jdc=None
233       self.appel=None
234       if self.sd : self.sd.supprime()
235       for concept in self.sdprods:
236          concept.supprime()
237       for etape in self.etapes:
238          etape.supprime()
239
240    def type_sdprod(self,co,t):
241       """
242            Cette methode a pour fonction de typer le concept co avec le type t
243             dans les conditions suivantes
244             1- co est un concept produit de self
245             2- co est un concept libre : on le type et on l attribue à self
246            Elle enregistre egalement les concepts produits (on fait l hypothese
247             que la liste sdprods a été correctement initialisee, vide probablement)
248       """
249       if not hasattr(co,'etape'):
250          # Le concept vaut None probablement. On ignore l'appel
251          return
252
253       if co.etape == None:
254          # le concept est libre
255          co.etape=self
256          co.__class__ = t
257          self.sdprods.append(co)
258       elif co.etape== self:
259          # le concept est produit par self
260          co.__class__ = t
261          self.sdprods.append(co)
262       elif co.etape== self.parent:
263          # le concept est produit par la macro superieure
264          # on transfere la propriete
265          # On verifie que le type du concept existant co.__class__ est un sur type de celui attendu
266          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
267          if not issubclass(t,co.__class__):
268             raise AsException("Le type du concept produit %s devrait etre une sur classe de %s" %(co.__class__,t))
269          co.etape=self
270          co.__class__ = t
271          self.sdprods.append(co)
272       elif self.issubstep(co.etape):
273          # Le concept est propriété d'une sous etape de self. Il doit etre considere
274          # comme produit par la macro => ajout dans self.sdprods
275          self.sdprods.append(co)
276       else:
277          # le concept est produit par une autre étape
278          return
279
280    def issubstep(self,etape):
281       """ 
282           Cette methode retourne un entier indiquant si etape est une
283           sous etape de la macro self ou non
284           1 = oui
285           0 = non
286       """
287       if etape in self.etapes:return 1
288       for etap in self.etapes:
289         if etap.issubstep(etape):return 1
290       return 0
291
292    def register(self,etape):
293       """ 
294           Enregistrement de etape dans le contexte de la macro : liste etapes 
295           et demande d enregistrement global aupres du JDC
296       """
297       self.etapes.append(etape)
298       idetape=self.jdc.g_register(etape)
299       return idetape
300
301    def reg_sd(self,sd):
302       """ 
303            Methode appelee dans l __init__ d un ASSD a sa creation pour
304            s enregistrer (reserve aux ASSD créés au sein d'une MACRO)
305       """
306       self.sds.append(sd)
307       return self.jdc.o_register(sd)
308
309    def create_sdprod(self,etape,nomsd):
310       """ 
311           Intention : Cette methode doit fabriquer le concept produit retourne
312                   par l'etape etape et le nommer.
313                   Elle est appelée à l'initiative de l'etape
314                   pendant le processus de construction de cette etape : methode __call__
315                   de la classe CMD (OPER ou MACRO)
316                   Ce travail est réalisé par le contexte supérieur (etape.parent)
317                   car dans certains cas, le concept ne doit pas etre fabriqué mais
318                   l'etape doit simplement utiliser un concept préexistant.
319                   Cas 1 : etape.reuse != None : le concept est réutilisé
320                   Cas 2 : l'étape appartient à une macro qui a déclaré un concept
321                           de sortie qui doit etre produit par cette etape.
322       """
323       if self.Outputs.has_key(nomsd):
324          # Il s'agit d'un concept de sortie de la macro. Il ne faut pas le créer
325          # Il faut quand meme appeler la fonction sd_prod si elle existe.
326          # get_type_produit le fait et donne le type attendu par la commande pour verification ultérieure.
327          sdprod=etape.get_type_produit()
328          sd=self.Outputs[nomsd]
329          # On verifie que le type du concept existant sd.__class__ est un sur type de celui attendu
330          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
331          if not issubclass(sdprod,sd.__class__):
332             raise AsException("Le type du concept produit %s devrait etre une sur classe de %s" %(sd.__class__,sdprod))
333          # La propriete du concept est transferee a l'etape avec le type attendu par l'étape
334          etape.sd=sd
335          #sd.__call__=sdprod
336          #XXX Il semble plus logique que ce soit class et non pas call ???
337          sd.__class__=sdprod
338          sd.etape=etape
339       else:
340          sd= etape.get_sd_prod()
341          if sd != None and etape.reuse == None:
342             # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation d un concept
343             self.NommerSdprod(sd,nomsd)
344       return sd
345
346    def NommerSdprod(self,sd,sdnom):
347       """ 
348           Cette methode est appelee par les etapes internes de la macro
349           La macro appelle le JDC pour valider le nommage
350           On considere que l espace de nom est unique et géré par le JDC
351           Si le nom est deja utilise, l appel leve une exception
352       """
353       #XXX attention inconsistence : prefix et gcncon ne sont pas 
354       # définis dans le package Noyau. La methode NommerSdprod pour
355       # les macros devrait peut etre etre déplacée dans Build ???
356       if CONTEXT.debug : print "MACRO.NommerSdprod: ",sd,sdnom
357       if hasattr(self,'prefix'):
358         # Dans le cas de l'include_materiau on ajoute un prefixe au nom du concept
359         if sdnom != self.prefix:sdnom=self.prefix+sdnom
360       if self.Outputs.has_key(sdnom):
361         # Il s'agit d'un concept de sortie de la macro produit par une sous commande
362         sdnom=self.Outputs[sdnom].nom
363       elif sdnom[0] == '_':
364         # Si le nom du concept commence par le caractere _ on lui attribue
365         # un identificateur JEVEUX construit par gcncon et respectant
366         # la regle gcncon legerement adaptee ici
367         # nom commencant par __ : il s'agit de concepts qui seront detruits
368         # nom commencant par _ : il s'agit de concepts intermediaires qui seront gardes
369         # ATTENTION : il faut traiter différemment les concepts dont le nom
370         # commence par _ mais qui sont des concepts nommés automatiquement par
371         # une éventuelle sous macro.
372         # Le test suivant n'est pas tres rigoureux mais permet de fonctionner pour le moment (a améliorer)
373         if sdnom[1] in string.digits:
374           # Ce concept provient probablement d'une macro appelee par self
375           pass
376         elif sdnom[1] == '_':
377           sdnom=self.gcncon('.')
378         else:
379           sdnom=self.gcncon('_')
380       if self.sd != None and self.sd.nom == sdnom :
381         # Il s'agit du concept produit par la macro, il a deja ete nomme.
382         # On se contente de donner le meme nom au concept produit par la sous commande
383         # sans passer par la routine de nommage
384         sd.nom=sdnom
385       else:
386         # On propage le nommage au contexte superieur
387         self.parent.NommerSdprod(sd,sdnom)
388
389    def delete_concept_after_etape(self,etape,sd):
390       """
391           Met à jour les étapes de la MACRO  qui sont après etape suite à
392           la disparition du concept sd
393       """
394       # Cette methode est définie dans le noyau mais ne sert que pendant la phase de creation
395       # des etapes et des concepts. Il n'y a aucun traitement particulier à réaliser
396       # Dans d'autres conditions, il faudrait surcharger cette méthode.
397       return
398
399    def accept(self,visitor):
400       """
401          Cette methode permet de parcourir l'arborescence des objets
402          en utilisant le pattern VISITEUR
403       """
404       visitor.visitMACRO_ETAPE(self)
405
406    def update_context(self,d):
407       """
408          Met à jour le contexte contenu dans le dictionnaire d
409          Une MACRO_ETAPE peut ajouter plusieurs concepts dans le contexte
410          Une fonction enregistree dans op_init peut egalement modifier le contexte
411       """
412       if type(self.definition.op_init) == types.FunctionType:
413         apply(self.definition.op_init,(self,d))
414       if self.sd != None:d[self.sd.nom]=self.sd
415       for co in self.sdprods:
416         d[co.nom]=co
417
418    def make_include(self,unite=None):
419       """
420           Inclut un fichier dont l'unite logique est unite
421       """
422       if not unite : return
423       f,text=self.get_file(unite=unite,fic_origine=self.parent.nom)
424       self.fichier_init = f
425       if f == None:return
426       self.make_contexte(f,text)
427
428    def make_poursuite(self):
429       """
430           Inclut un fichier poursuite
431       """
432       f,text=self.get_file(fic_origine=self.parent.nom)
433       self.fichier_init=f
434       if f == None:return
435       self.make_contexte(f,text)
436
437    def make_contexte(self,f,text):
438       """
439           Interprete le texte fourni (text) issu du fichier f
440           dans le contexte du parent.
441           Cette methode est utile pour le fonctionnement des
442           INCLUDE
443       """
444       # on execute le texte fourni dans le contexte forme par
445       # le contexte de l etape pere (global au sens Python)
446       # et le contexte de l etape (local au sens Python)
447       code=compile(text,f,'exec')
448       d={}
449       self.g_context = d
450       self.contexte_fichier_init = d
451       exec code in self.parent.g_context,d
452
453
454
455
456
457
458