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