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