Salome HOME
premiere version
[tools/eficas.git] / Noyau / N_MACRO_ETAPE.py
1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2013   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
21 """
22     Ce module contient la classe MACRO_ETAPE qui sert à vérifier et à exécuter
23     une commande
24 """
25
26 # Modules Python
27 import types,sys,string
28 import traceback
29 from warnings import warn
30
31 # Modules EFICAS
32 import N_MCCOMPO
33 import N_ETAPE
34 from N_Exception import AsException
35 import N_utils
36 from N_utils import AsType
37 from N_CO import CO
38 from N_ASSD import ASSD
39 from N_info import message, SUPERV
40
41 class MACRO_ETAPE(N_ETAPE.ETAPE):
42    """
43
44    """
45    nature = "COMMANDE"
46    typeCO=CO
47    def __init__(self,oper=None,reuse=None,args={}):
48       """
49       Attributs :
50          - definition : objet portant les attributs de définition d'une étape
51            de type macro-commande. Il est initialisé par
52            l'argument oper.
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          - valeur : arguments d'entrée de type mot-clé=valeur. Initialisé
57            avec l'argument args.
58       """
59       N_ETAPE.ETAPE.__init__(self, oper, reuse, args, niveau=5)
60       self.g_context = {}
61       # Contexte courant
62       self.current_context = {}
63       self.macro_const_context = {}
64       self.index_etape_courante = 0
65       self.etapes = []
66       self.index_etapes = {}
67       #  Dans le cas d'une macro écrite en Python, l'attribut Outputs est un
68       #  dictionnaire qui contient les concepts produits de sortie
69       #  (nom : ASSD) déclarés dans la fonction sd_prod
70       self.Outputs = {}
71       self.sdprods = []
72       self.UserError = "UserError"
73       # permet de stocker le nom du dernier concept nommé dans la macro
74       self.last = None
75
76    def make_register(self):
77       """
78       Initialise les attributs jdc, id, niveau et réalise les enregistrements
79       nécessaires
80       """
81       N_ETAPE.ETAPE.make_register(self)
82       if self.parent :
83          self.UserError=self.jdc.UserError
84       else:
85          self.UserError="UserError"
86
87    def Build_sd(self,nom):
88       """
89          Construit le concept produit de l'opérateur. Deux cas
90          peuvent se présenter :
91
92            - le parent n'est pas défini. Dans ce cas, l'étape prend en charge
93              la création et le nommage du concept.
94
95            - le parent est défini. Dans ce cas, l'étape demande au parent la
96              création et le nommage du concept.
97
98       """
99       #message.debug(SUPERV, "%s", self.nom)
100       self.sdnom=nom
101       try:
102          # On positionne la macro self en tant que current_step pour que les
103          # étapes créées lors de l'appel à sd_prod et à op_init aient la macro
104          #  comme parent
105          self.set_current_step()
106          if self.parent:
107             sd= self.parent.create_sdprod(self,nom)
108             if type(self.definition.op_init) == types.FunctionType:
109                apply(self.definition.op_init,(self,self.parent.g_context))
110          else:
111             sd=self.get_sd_prod()
112             if sd != None and self.reuse == None:
113                # On ne nomme le concept que dans le cas de non reutilisation
114                # d un concept
115                sd.set_name(nom)
116          self.reset_current_step()
117       except AsException,e:
118          self.reset_current_step()
119          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
120                               'fichier : ',self.appel[1],e)
121       except (EOFError, self.UserError):
122          # Le retablissement du step courant n'est pas strictement necessaire. On le fait pour des raisons de coherence
123          self.reset_current_step()
124          raise
125       except :
126          self.reset_current_step()
127          l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
128          raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
129                            'fichier : ',self.appel[1]+'\n',
130                             string.join(l))
131
132       self.Execute()
133       return sd
134
135    def get_sd_prod(self):
136       """
137         Retourne le concept résultat d'une macro étape
138         La difference avec une etape ou une proc-etape tient a ce que
139         le concept produit peut exister ou pas
140
141         Si sd_prod == None le concept produit n existe pas on retourne None
142
143         Deux cas :
144          - cas 1 : sd_prod  n'est pas une fonction
145                  il s'agit d'une sous classe de ASSD
146                  on construit le sd à partir de cette classe
147                  et on le retourne
148          - cas 2 : sd_prod est une fonction
149                  on l'évalue avec les mots-clés de l'étape (mc_liste)
150                  on construit le sd à partir de la classe obtenue
151                  et on le retourne
152       """
153       sd_prod=self.definition.sd_prod
154       self.typret=None
155
156       if type(self.definition.sd_prod) == types.FunctionType:
157         d=self.cree_dict_valeurs(self.mc_liste)
158         try:
159           # la sd_prod d'une macro a l'objet macro_etape lui meme en premier argument
160           # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
161           # les concepts produits dans self.sdprods, il faut le mettre à zéro avant de l'appeler
162           self.sdprods=[]
163           sd_prod= apply(sd_prod,(self,),d)
164         except (EOFError,self.UserError):
165           raise
166         except:
167           if CONTEXT.debug: traceback.print_exc()
168           l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
169           raise AsException("impossible d affecter un type au resultat\n",string.join(l[2:]))
170
171       # on teste maintenant si la SD est réutilisée ou s'il faut la créer
172       if self.definition.reentrant != 'n' and self.reuse:
173         # Le concept produit est specifie reutilise (reuse=xxx). C'est une erreur mais non fatale.
174         # Elle sera traitee ulterieurement.
175         self.sd=self.reuse
176       else:
177         if sd_prod == None:
178           self.sd=None
179         else:
180           self.sd= sd_prod(etape=self)
181           self.typret=sd_prod
182           # Si la commande est obligatoirement reentrante et reuse n'a pas ete specifie, c'est une erreur.
183           # On ne fait rien ici. L'erreur sera traitee par la suite.
184       # précaution
185       if self.sd is not None and not isinstance(self.sd, ASSD):
186          raise AsException("""
187 Impossible de typer le résultat !
188 Causes possibles :
189    Utilisateur : Soit la valeur fournie derrière "reuse" est incorrecte,
190                  soit il y a une "," à la fin d'une commande précédente.
191    Développeur : La fonction "sd_prod" retourne un type invalide.""")
192       return self.sd
193
194    def get_type_produit(self,force=0):
195       try:
196           return self.get_type_produit_brut(force)
197       except:
198           #traceback.print_exc()
199           return None
200
201    def get_type_produit_brut(self,force=0):
202       """
203            Retourne le type du concept résultat de l'étape et eventuellement type
204            les concepts produits "à droite" du signe égal (en entrée)
205
206            Deux cas :
207              - cas 1 : sd_prod de oper n'est pas une fonction
208                     il s'agit d'une sous classe de ASSD
209                     on retourne le nom de la classe
210              - cas 2 : il s'agit d'une fonction
211                     on l'évalue avec les mots-clés de l'étape (mc_liste)
212                     et on retourne son résultat
213       """
214       if not force and hasattr(self,'typret'):
215           return self.typret
216
217       if type(self.definition.sd_prod) == types.FunctionType:
218         d=self.cree_dict_valeurs(self.mc_liste)
219         # Comme sd_prod peut invoquer la méthode type_sdprod qui ajoute
220         # les concepts produits dans self.sdprods, il faut le mettre à zéro
221         self.sdprods=[]
222         sd_prod= apply(self.definition.sd_prod,(self,),d)
223       else:
224         sd_prod=self.definition.sd_prod
225       return sd_prod
226
227    def get_contexte_avant(self,etape):
228       """
229           Retourne le dictionnaire des concepts connus avant etape
230           pour les commandes internes a la macro
231           On tient compte des commandes qui modifient le contexte
232           comme DETRUIRE ou les macros
233       """
234       # L'étape courante pour laquelle le contexte a été calculé est
235       # mémorisée dans self.index_etape_courante
236       #message.debug(SUPERV, "g_context : %s", [k for k, v in self.g_context.items() if isinstance(v, ASSD)])
237       #message.debug(SUPERV, "current_context : %s", [k for k, v in self.current_context.items() if isinstance(v, ASSD)])
238       d = self.current_context = self.g_context.copy()
239       if etape is None:
240           return d
241       # retirer les sd produites par 'etape'
242       sd_names = [sd.nom for sd in etape.get_created_sd()]
243       #message.debug(SUPERV, "etape: %s, reuse : %s, sdprods de %s : %s",
244                     #self.nom, etape.reuse, etape.nom, sd_names)
245       for nom in sd_names:
246          try:
247              del d[nom]
248          except KeyError:
249              pass
250              # Exemple avec INCLUDE_MATERIAU appelé dans une macro.
251              # Les fonctions restent uniquement dans le contexte de INCLUDE_MATERIAU,
252              # elles ne sont donc pas dans le contexte de la macro appelante.
253              #from warnings import warn
254              #warn("concept '%s' absent du contexte de %s" % (nom, self.nom),
255                   #RuntimeWarning, stacklevel=2)
256       return d
257
258    def supprime(self):
259       """
260          Méthode qui supprime toutes les références arrières afin que
261          l'objet puisse etre correctement détruit par le garbage collector
262       """
263       N_MCCOMPO.MCCOMPO.supprime(self)
264       self.jdc=None
265       self.appel=None
266       if self.sd : self.sd.supprime()
267       for concept in self.sdprods:
268          concept.supprime()
269       for etape in self.etapes:
270          etape.supprime()
271
272    def clean(self, netapes):
273       """Nettoie les `netapes` dernières étapes de la liste des étapes."""
274       if self.jdc.hist_etape:
275           return
276       for i in xrange(netapes):
277         e=self.etapes.pop()
278         jdc=e.jdc
279         parent=e.parent
280         e.supprime()
281         e.parent=parent
282         e.jdc=jdc
283         #message.debug(SUPERV, "MACRO.clean - etape = %s - refcount(e) = %d",
284                       #e.nom, sys.getrefcount(e))
285         del self.index_etapes[e]
286
287    def type_sdprod(self,co,t):
288       """
289            Cette methode a pour fonction de typer le concept co avec le type t
290            dans les conditions suivantes :
291             1. co est un concept produit de self
292             2. co est un concept libre : on le type et on l attribue à self
293
294            Elle enregistre egalement les concepts produits (on fait l hypothese
295            que la liste sdprods a été correctement initialisee, vide probablement)
296       """
297       if not hasattr(co,'etape'):
298          # Le concept vaut None probablement. On ignore l'appel
299          return
300       #
301       # On cherche a discriminer les differents cas de typage d'un concept
302       # produit par une macro qui est specifie dans un mot cle simple.
303       # On peut passer plusieurs fois par type_sdprod ce qui explique
304       # le nombre important de cas.
305       #
306       # Cas 1 : Le concept est libre. Il vient d'etre cree par CO(nom)
307       # Cas 2 : Le concept est produit par la macro. On est deja passe par type_sdprod.
308       #         Cas semblable a Cas 1.
309       # Cas 3 : Le concept est produit par la macro englobante (parent). On transfere
310       #         la propriete du concept de la macro parent a la macro courante (self)
311       #         en verifiant que le type est valide
312       # Cas 4 : La concept est la propriete d'une etape fille. Ceci veut dire qu'on est
313       #         deja passe par type_sdprod et que la propriete a ete transfere a une
314       #         etape fille. Cas semblable a Cas 3.
315       # Cas 5 : Le concept est produit par une etape externe a la macro.
316       #
317       if co.etape == None:
318          # Cas 1 : le concept est libre
319          # On l'attache a la macro et on change son type dans le type demande
320          # Recherche du mot cle simple associe au concept
321          mcs=self.get_mcs_with_co(co)
322          if len(mcs) != 1:
323             raise AsException("""Erreur interne.
324 Il ne devrait y avoir qu'un seul mot cle porteur du concept CO (%s)""" % co)
325          mcs=mcs[0]
326          if not self.typeCO in mcs.definition.type:
327             raise AsException("""Erreur interne.
328 Impossible de changer le type du concept (%s). Le mot cle associe ne supporte pas CO mais seulement (%s)""" %(co,mcs.definition.type))
329          co.etape = self
330          # affectation du bon type du concept
331          #message.debug(SUPERV, "MACRO.type_sdprod : changement de type de %s --> %s", co, t)
332          co.change_type(t)
333          self.sdprods.append(co)
334
335       elif co.etape == self:
336          # Cas 2 : le concept est produit par la macro (self)
337          # On est deja passe par type_sdprod (Cas 1 ou 3).
338          #XXX Peut-il être créer par une autre macro ?
339          #    On vérifie juste que c'est un vrai CO non déjà typé
340          #if co.etape == co._etape: 
341          if co.is_typco() == 1:
342            #Le concept a été créé par la macro (self)
343            #On peut changer son type
344            co.change_type(t)
345          else:
346            #Le concept a été créé par une macro parente
347            # Le type du concept doit etre coherent avec le type demande (seulement derive)
348            if not isinstance(co,t):
349              raise AsException("""Erreur interne.
350 Le type demande (%s) et le type du concept (%s) devraient etre derives""" %(t,co.__class__))
351
352          self.sdprods.append(co)
353
354       elif co.etape== self.parent:
355          # Cas 3 : le concept est produit par la macro parente (self.parent)
356          # on transfere la propriete du concept a la macro fille
357          # et on change le type du concept comme demande
358          # Au prealable, on verifie que le concept existant (co) est une instance
359          # possible du type demande (t)
360          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
361          if not isinstance(co,t):
362             raise AsException("""
363 Impossible de changer le type du concept produit (%s) en (%s).
364 Le type actuel (%s) devrait etre une classe derivee du nouveau type (%s)""" % (co,t,co.__class__,t))
365          mcs=self.get_mcs_with_co(co)
366          if len(mcs) != 1:
367             raise AsException("""Erreur interne.
368 Il ne devrait y avoir qu'un seul mot cle porteur du concept CO (%s)""" % co)
369          mcs=mcs[0]
370          if not self.typeCO in mcs.definition.type:
371             raise AsException("""Erreur interne.
372 Impossible de changer le type du concept (%s). Le mot cle associe ne supporte pas CO mais seulement (%s)""" %(co,mcs.definition.type))
373          co.etape=self
374          # On ne change pas le type car il respecte la condition isinstance(co,t)
375          #co.__class__ = t
376          self.sdprods.append(co)
377
378       elif self.issubstep(co.etape):
379          # Cas 4 : Le concept est propriété d'une sous etape de la macro (self).
380          # On est deja passe par type_sdprod (Cas 3 ou 1).
381          # Il suffit de le mettre dans la liste des concepts produits (self.sdprods)
382          # Le type du concept et t doivent etre derives.
383          # Il n'y a aucune raison pour que la condition ne soit pas verifiee.
384          if not isinstance(co,t):
385             raise AsException("""Erreur interne.
386 Le type demande (%s) et le type du concept (%s) devraient etre derives""" %(t,co.__class__))
387          self.sdprods.append(co)
388
389       else:
390          # Cas 5 : le concept est produit par une autre étape
391          # On ne fait rien
392          return
393
394    def issubstep(self,etape):
395       """
396           Cette methode retourne un entier indiquant si etape est une
397           sous etape de la macro self ou non
398           1 = oui
399           0 = non
400       """
401       if etape in self.etapes:return 1
402       for etap in self.etapes:
403         if etap.issubstep(etape):return 1
404       return 0
405
406    def register(self,etape):
407       """
408           Enregistrement de etape dans le contexte de la macro : liste etapes
409           et demande d enregistrement global aupres du JDC
410       """
411       self.etapes.append(etape)
412       self.index_etapes[etape] = len(self.etapes) - 1
413       idetape=self.jdc.g_register(etape)
414       return idetape
415
416    def reg_sd(self,sd):
417       """
418            Methode appelee dans l __init__ d un ASSD a sa creation pour
419            s enregistrer (reserve aux ASSD créés au sein d'une MACRO)
420       """
421       return self.jdc.o_register(sd)
422
423    def create_sdprod(self,etape,nomsd):
424       """
425           Cette methode doit fabriquer le concept produit retourne
426           par l'etape etape et le nommer.
427
428           Elle est appelée à l'initiative de l'etape
429           pendant le processus de construction de cette etape : methode __call__
430           de la classe CMD (OPER ou MACRO)
431           Ce travail est réalisé par le contexte supérieur (etape.parent)
432           car dans certains cas, le concept ne doit pas etre fabriqué mais
433           l'etape doit simplement utiliser un concept préexistant.
434                   - Cas 1 : etape.reuse != None : le concept est réutilisé
435                   - Cas 2 : l'étape appartient à une macro qui a déclaré un concept
436                     de sortie qui doit etre produit par cette etape.
437       """
438       if self.Outputs.has_key(nomsd):
439          # Il s'agit d'un concept de sortie de la macro. Il ne faut pas le créer
440          # Il faut quand meme appeler la fonction sd_prod si elle existe.
441          # get_type_produit le fait et donne le type attendu par la commande pour verification ultérieure.
442          sdprod=etape.get_type_produit_brut()
443          sd=self.Outputs[nomsd]
444          # On verifie que le type du concept existant sd.__class__ est un sur type de celui attendu
445          # Cette règle est normalement cohérente avec les règles de vérification des mots-clés
446          if not issubclass(sdprod,sd.__class__):
447             raise AsException("Le type du concept produit %s devrait etre une sur classe de %s" %(sd.__class__,sdprod))
448          # La propriete du concept est transferee a l'etape avec le type attendu par l'étape
449          etape.sd=sd
450          sd.etape=etape
451          if self.reuse == sd and etape.reuse != sd \
452                 and getattr(sd, "executed", 0) == 1: # n'a pas été pas détruit
453             raise AsException("Le concept '%s' est réentrant dans la macro-commande %s. " \
454                               "Il devrait donc l'être dans %s (produit sous le nom '%s')." \
455                                 % (sd.nom, self.nom, etape.nom, nomsd))
456          # On donne au concept le type produit par la sous commande.
457          # Le principe est le suivant : apres avoir verifie que le type deduit par la sous commande
458          # est bien coherent avec celui initialement affecte par la macro (voir ci dessus)
459          # on affecte au concept ce type car il peut etre plus precis (derive, en general)
460          sd.__class__=sdprod
461          # On force également le nom stocké dans l'attribut sdnom : on lui donne le nom
462          # du concept associé à nomsd
463          etape.sdnom=sd.nom
464          # pour l'ajouter au contexte de la macro
465          self.g_context[sd.nom] = sd
466       elif etape.definition.reentrant != 'n' and etape.reuse != None:
467          # On est dans le cas d'une commande avec reutilisation d'un concept existant
468          # get_sd_prod fait le necessaire : verifications, associations, etc. mais ne cree
469          # pas un nouveau concept. Il retourne le concept reutilise
470          sd= etape.get_sd_prod()
471          # Dans le cas d'un concept nomme automatiquement : _xxx, __xxx,
472          # On force le nom stocke dans l'attribut sdnom  de l'objet etape : on lui donne le nom
473          # du concept  reutilise (sd ou etape.reuse c'est pareil)
474          # Ceci est indispensable pour eviter des erreurs lors des verifications des macros
475          # En effet une commande avec reutilisation d'un concept verifie que le nom de
476          # la variable a gauche du signe = est le meme que celui du concept reutilise.
477          # Lorsqu'une telle commande apparait dans une macro, on supprime cette verification.
478          if (etape.sdnom == '' or etape.sdnom[0] == '_'):
479             etape.sdnom=sd.nom
480       else:
481          # On est dans le cas de la creation d'un nouveau concept
482          sd= etape.get_sd_prod()
483          if sd != None :
484             self.NommerSdprod(sd,nomsd)
485       return sd
486
487    def NommerSdprod(self,sd,sdnom,restrict='non'):
488         """
489           Cette méthode est appelée par les etapes internes de la macro.
490           La macro appelle le JDC pour valider le nommage.
491           On considère que l'espace de nom est unique et géré par le JDC.
492           Si le nom est déjà utilisé, l'appel lève une exception.
493           Si restrict=='non', on insère le concept dans le contexte du parent de la macro.
494           Si restrict=='oui', on insère le concept uniquement dans le contexte de la macro.
495         """
496         # Normalement, lorsqu'on appelle cette methode, on ne veut nommer que des concepts nouvellement crees.
497         # Le filtrage sur les concepts a creer ou a ne pas creer est fait dans la methode
498         # create_sdprod. La seule chose a verifier apres conversion eventuelle du nom
499         # est de verifier que le nom n'est pas deja attribue. Ceci est fait en delegant
500         # au JDC par l'intermediaire du parent.
501         #message.debug(SUPERV, "macro results = %s, (sdnom: %r, restrict: %r)",
502                       #self.Outputs.keys(), sdnom, restrict)
503         if self.Outputs.has_key(sdnom):
504             # Il s'agit d'un concept de sortie de la macro produit par une sous commande
505             sdnom = self.Outputs[sdnom].nom
506         elif len(sdnom) > 0:
507             if sdnom[0] in ('_', '.') and sdnom[1:].isdigit():
508                 # il est déjà de la forme _9000012 ou .9000017
509                 pass
510             elif sdnom[0] == '_':
511                 # Si le nom du concept commence par le caractère '_', on lui attribue
512                 # un identificateur JEVEUX construit par gcncon.
513                 # nom commençant par __ : il s'agit de concepts qui seront détruits
514                 # nom commençant par _ : il s'agit de concepts intermediaires qui seront gardés
515                 if len(sdnom) > 1 and sdnom[1] == '_':
516                     sdnom = self.gcncon('.')
517                 else:
518                     sdnom = self.gcncon('_')
519             elif self.nom in ('INCLUDE', 'MACR_RECAL'):
520                 # dans le cas d'INCLUDE, on passe
521                 # MACR_RECAL fonctionne comme INCLUDE
522                 pass
523             else:
524                 # On est dans le cas d'un nom de concept global
525                 #XXX à voir, création de CO() dans CALC_ESSAI (sdls139a)
526                 if not sd.is_typco():
527                     raise AsException("Résultat non déclaré par la macro %s : %s" % (self.nom, sdnom))
528         self.last = sdnom
529         if restrict == 'non':
530             # On demande le nommage au parent mais sans ajout du concept dans le contexte du parent
531             # car on va l'ajouter dans le contexte de la macro
532             self.parent.NommerSdprod(sd,sdnom,restrict='oui')
533             # On ajoute dans le contexte de la macro les concepts nommes
534             # Ceci est indispensable pour les CO (macro) dans un INCLUDE
535             self.g_context[sdnom]=sd
536             #message.debug(SUPERV, "g_context[%s] = %s", sdnom, sd)
537         else:
538             # La demande de nommage vient probablement d'une macro qui a mis
539             # le concept dans son contexte. On ne traite plus que le nommage (restrict="oui")
540             #message.debug(SUPERV, "restrict=oui  co[%s] = %s", sdnom, sd)
541             self.parent.NommerSdprod(sd,sdnom,restrict='oui')
542
543    def delete_concept_after_etape(self,etape,sd):
544       """
545           Met à jour les étapes de la MACRO  qui sont après etape suite à
546           la disparition du concept sd
547       """
548       # Cette methode est définie dans le noyau mais ne sert que pendant la phase de creation
549       # des etapes et des concepts. Il n'y a aucun traitement particulier à réaliser
550       # Dans d'autres conditions, il faudrait surcharger cette méthode.
551       return
552
553    def get_created_sd(self):
554       """Retourne la liste des sd réellement produites par l'étape.
555       Si reuse est présent, `self.sd` a été créée avant, donc n'est pas dans
556       cette liste."""
557       sdprods = self.sdprods[:]
558       if not self.reuse and self.sd:
559           sdprods.append(self.sd)
560       return sdprods
561
562    def get_last_concept(self):
563        """Retourne le dernier concept produit dans la macro.
564        Peut-être utile pour accéder au contenu 'fortran' dans une
565        clause 'except'."""
566        return self.g_context.get(self.last, None)
567
568    def accept(self,visitor):
569       """
570          Cette methode permet de parcourir l'arborescence des objets
571          en utilisant le pattern VISITEUR
572       """
573       visitor.visitMACRO_ETAPE(self)
574
575    def update_context(self,d):
576       """
577          Met à jour le contexte contenu dans le dictionnaire d
578          Une MACRO_ETAPE peut ajouter plusieurs concepts dans le contexte
579          Une fonction enregistree dans op_init peut egalement modifier le contexte
580       """
581       if type(self.definition.op_init) == types.FunctionType:
582         apply(self.definition.op_init,(self,d))
583       if self.sd != None:d[self.sd.nom]=self.sd
584       for co in self.sdprods:
585         d[co.nom]=co
586
587    def make_include(self, unite=None, fname=None):
588       """Inclut un fichier dont l'unite logique est `unite` ou de nom `fname`"""
589       if unite is not None:
590          warn("'unite' is deprecated, please use 'fname' instead",
591               DeprecationWarning, stacklevel=2)
592          fname = 'fort.%s' % unite
593       if not fname:
594          return
595       f, text = self.get_file(fic_origine=self.parent.nom, fname=fname)
596       self.fichier_init = f
597       if f == None:
598          return
599       self.make_contexte(f, text)
600
601    def make_poursuite(self):
602       """Inclut un fichier poursuite"""
603       raise NotImplementedError('this method must be derivated (in Eficas)')
604
605    def make_contexte(self,f,text):
606       """
607           Interprete le texte fourni (text) issu du fichier f
608           dans le contexte du parent.
609           Cette methode est utile pour le fonctionnement des
610           INCLUDE
611       """
612       # on execute le texte fourni dans le contexte forme par
613       # le contexte de l etape pere (global au sens Python)
614       # et le contexte de l etape (local au sens Python)
615       code = compile(text,f,'exec')
616       d = self.g_context = self.macro_const_context
617       globs = self.get_global_contexte()
618       d.update(globs)
619       exec code in globs, d
620       # pour ne pas conserver des références sur tout
621       self.macro_const_context = {}
622
623    def get_global_contexte(self):
624       """
625           Cette methode retourne le contexte global fourni
626           par le parent(self) a une etape fille (l'appelant) pour
627           realiser des evaluations de texte Python (INCLUDE,...)
628       """
629       # Le contexte global est forme par concatenation du contexte
630       # du parent de self et de celui de l'etape elle meme (self)
631       # Pour les concepts, cela ne doit rien changer. Mais pour les constantes,
632       # les valeurs de get_contexte_avant sont moins récentes que dans
633       # get_global_contexte. On prend donc la précaution de ne pas écraser
634       # ce qui y est déjà.
635       d = self.parent.get_global_contexte()
636       d.update( self.g_context )
637       d.update( [(k, v) for k, v in self.parent.get_contexte_avant(self).items()
638                         if d.get(k) is None] )
639       return d
640
641    def get_contexte_courant(self, etape_fille_du_jdc=None):
642       """
643          Retourne le contexte tel qu'il est au moment de l'exécution de
644          l'étape courante.
645       """
646       ctx = {}
647       # update car par ricochet on modifierait jdc.current_context
648       ctx.update( self.parent.get_contexte_courant(self) )
649       # on peut mettre None car toujours en PAR_LOT='NON', donc la dernière
650       ctx.update( self.get_contexte_avant(None) )
651       return ctx
652
653    def get_concept(self, nomsd):
654       """
655           Méthode pour recuperer un concept à partir de son nom
656           dans le contexte du jdc connu avant l'exécution de la macro courante.
657       """
658       # chercher dans self.get_contexte_avant, puis si non trouve
659       # self.parent.get_concept est peut-etre plus performant
660       co = self.get_contexte_courant().get(nomsd.strip(), None)
661       if not isinstance(co, ASSD):
662           co = None
663       return co
664
665    def get_concept_by_type(self, nomsd, typesd, etape=None):
666       """
667           Méthode pour récuperer un concept à partir de son nom et de son type.
668           Il aura comme père 'etape' (ou la macro courante si etape est absente).
669       """
670       return self.parent.get_concept_by_type(nomsd, typesd, etape=etape or self)
671       
672    def copy(self):
673       """ Méthode qui retourne une copie de self non enregistrée auprès du JDC
674           et sans sd
675           On surcharge la methode de ETAPE pour exprimer que les concepts crees
676           par la MACRO d'origine ne sont pas crees par la copie mais eventuellement
677           seulement utilises
678       """
679       etape=N_ETAPE.ETAPE.copy(self)
680       etape.sdprods=[]
681       return etape
682
683    def copy_intern(self,etape):
684       """ Cette méthode effectue la recopie des etapes internes d'une macro
685           passée en argument (etape)
686       """
687       self.etapes=[]
688       self.index_etapes={}
689       for etp in etape.etapes:
690           new_etp=etp.copy()
691           new_etp.copy_reuse(etp)
692           new_etp.copy_sdnom(etp)
693           new_etp.reparent(self)
694           if etp.sd:
695              new_sd = etp.sd.__class__(etape=new_etp)
696              new_etp.sd = new_sd
697              if etp.reuse:
698                 new_sd.set_name(etp.sd.nom)
699              else:
700                 self.NommerSdprod(new_sd,etp.sd.nom)
701           new_etp.copy_intern(etp)
702           self.etapes.append(new_etp)
703           self.index_etapes[new_etp] = len(self.etapes) - 1
704
705
706    def reset_jdc(self,new_jdc):
707        """
708           Reinitialise l'etape avec un nouveau jdc parent new_jdc
709        """
710        if self.sd and self.reuse == None :
711            self.parent.NommerSdprod(self.sd,self.sd.nom)
712        for concept in self.sdprods:
713            self.parent.NommerSdprod(concept,concept.nom)
714
715    def reparent(self,parent):
716        """
717          Cette methode sert a reinitialiser la parente de l'objet
718        """
719        N_ETAPE.ETAPE.reparent(self,parent)
720        #on ne change pas la parenté des concepts. On s'assure uniquement que le jdc en référence est le bon
721        for concept in self.sdprods:
722            concept.jdc=self.jdc
723        for e in self.etapes:
724            e.reparent(self)
725
726    def update_const_context(self, d):
727       """
728          Met à jour le contexte des constantes pour l'évaluation de
729          formules dans la macro.
730       """
731       # Dans le jdc, const_context est mis à jour par exec_compile
732       # Dans la macro, on n'a pas le code à compiler pour récupèrer les
733       # constantes locales à la macro. On demande donc explicitement de
734       # définir les constantes "locales".
735       self.macro_const_context.update(d)
736
737    def sd_accessible(self):
738       """On peut acceder aux "valeurs" (jeveux) des ASSD dans
739       les macro-commandes qui sont localement en PAR_LOT="NON"
740       sauf pour INCLUDE.
741       """
742       if CONTEXT.debug: print ' `- MACRO sd_accessible :', self.nom
743       return self.parent.sd_accessible() or not self.is_include()
744