]> SALOME platform Git repositories - tools/eficas.git/blob - Noyau/N_JDC.py
Salome HOME
Modif V6_4_°
[tools/eficas.git] / Noyau / N_JDC.py
1 #@ MODIF N_JDC Noyau  DATE 25/10/2011   AUTEUR COURTOIS M.COURTOIS 
2 # -*- coding: iso-8859-1 -*-
3 # RESPONSABLE COURTOIS M.COURTOIS
4 #            CONFIGURATION MANAGEMENT OF EDF VERSION
5 # ======================================================================
6 # COPYRIGHT (C) 1991 - 2011  EDF R&D                  WWW.CODE-ASTER.ORG
7 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
8 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
9 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
10 # (AT YOUR OPTION) ANY LATER VERSION.
11 #
12 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
13 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
14 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
15 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
16 #
17 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
18 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
19 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
20 #
21 #
22 # ======================================================================
23
24
25 """
26    Ce module contient la classe JDC qui sert à interpréter un jeu de commandes
27 """
28
29 # Modules Python
30 import os,string,traceback
31 import types,sys,linecache
32
33 # Modules EFICAS
34 import N_OBJECT
35 import N_CR
36 from N_Exception import AsException
37 from N_ASSD import ASSD
38 from N_info import message, SUPERV
39
40
41 MemoryErrorMsg = """MemoryError :
42
43 En général, cette erreur se produit car la mémoire utilisée hors du fortran
44 (jeveux) est importante.
45
46 Causes possibles :
47    - le calcul produit de gros objets Python dans une macro-commande ou
48      dans le jeu de commande lui-même,
49    - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
50      qui a besoin de mémoire hors jeveux,
51    - utilisation de jeveux dynamique,
52    - ...
53
54 Solution :
55    - distinguer la mémoire limite du calcul (case "Mémoire totale" de astk)
56      de la mémoire réservée à jeveux (case "dont Aster"), le reste étant
57      disponible pour les allocations dynamiques.
58 """
59
60
61
62 class JDC(N_OBJECT.OBJECT):
63    """
64       Cette classe interprete un jeu de commandes fourni sous
65       la forme d'une chaine de caractères
66
67       Attributs de classe :
68
69       Attributs d'instance :
70
71    """
72    nature = "JDC"
73    CR=N_CR.CR
74    exec_init="""
75 import Accas
76 from Accas import _F
77 from Accas import *
78 NONE = None
79 """
80
81    from N_utils import SEP
82
83    def __init__(self,definition=None,procedure=None,cata=None,
84                      cata_ord_dico=None,parent=None,
85                      nom='SansNom',appli=None,context_ini=None,**args):
86       self.procedure=procedure
87       self.definition = definition
88       self.cata=cata
89       if type(self.cata) != types.TupleType and cata != None:
90          self.cata=(self.cata,)
91       self._build_reserved_kw_list()
92       self.cata_ordonne_dico=cata_ord_dico
93       self.nom = nom
94       self.appli=appli
95       self.parent=parent
96       self.context_ini=context_ini
97       # On conserve les arguments supplémentaires. Il est possible de passer
98       # des informations globales au JDC par ce moyen. Il pourrait etre plus
99       # sur de mettre en place le mecanisme des mots-cles pour verifier la
100       # validité des valeurs passées.
101       # Ceci reste à faire
102       # On initialise avec les parametres de la definition puis on
103       # update avec ceux du JDC
104       self.args=self.definition.args
105       self.args.update(args)
106       self.nstep=0
107       self.nsd=0
108       self.par_lot='OUI'
109       if definition:
110          self.regles=definition.regles
111          self.code = definition.code
112       else:
113          self.regles=()
114          self.code = "CODE"
115       #
116       #  Creation de l objet compte rendu pour collecte des erreurs
117       #
118       self.cr = self.CR(debut = "CR phase d'initialisation",
119                         fin = "fin CR phase d'initialisation")
120       # on met le jdc lui-meme dans le context global pour l'avoir sous
121       # l'etiquette "jdc" dans le fichier de commandes
122       self.g_context={ 'jdc' : self }
123       # Liste pour stocker tous les concepts produits dans le JDC
124       self.sds=[]
125       # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par le nom)
126       self.sds_dict={}
127       self.etapes=[]
128       self.index_etapes = {}
129       self.mc_globaux={}
130       self.current_context={}
131       self.condition_context={}
132       self.index_etape_courante=0
133       self.UserError="UserError"
134       self.alea = None
135
136    def compile(self):
137       """
138          Cette methode compile la chaine procedure
139          Si des erreurs se produisent, elles sont consignées dans le
140          compte-rendu self.cr
141       """
142       try:
143          if self.appli != None :
144             self.appli.affiche_infos('Compilation du fichier de commandes en cours ...')
145          self.proc_compile=compile(self.procedure,self.nom,'exec')
146       except SyntaxError, e:
147          if CONTEXT.debug : traceback.print_exc()
148          l=traceback.format_exception_only(SyntaxError,e)
149          self.cr.exception("Compilation impossible : "+string.join(l))
150       except MemoryError, e:
151          self.cr.exception(MemoryErrorMsg)
152       except SystemError, e:
153          erreurs_connues = """
154 Causes possibles :
155  - offset too large : liste trop longue derrière un mot-clé.
156    Solution : liste = (valeurs, ..., )
157               MOT_CLE = *liste,
158 """
159          l=traceback.format_exception_only(SystemError,e)
160          l.append(erreurs_connues)
161          self.cr.exception("Compilation impossible : " + ''.join(l))
162       return
163
164    def exec_compile(self):
165       """
166          Cette méthode execute le jeu de commandes compilé dans le contexte
167          self.g_context de l'objet JDC
168       """
169       CONTEXT.set_current_step(self)
170       # Le module nommage utilise le module linecache pour accéder
171       # au source des commandes du jeu de commandes.
172       # Dans le cas d'un fichier, on accède au contenu de ce fichier
173       # Dans le cas d'une chaine de caractères il faut accéder
174       # aux commandes qui sont dans la chaine
175       import linecache
176       linecache.cache[self.nom]=0,0,string.split(self.procedure,'\n'),self.nom
177       try:
178          exec self.exec_init in self.g_context
179          for obj_cata in self.cata:
180             if type(obj_cata) == types.ModuleType :
181                init2 = "from "+obj_cata.__name__+" import *"
182                exec init2 in self.g_context
183
184          # Initialisation du contexte global pour l'évaluation des conditions de BLOC
185          # On utilise une copie de l'initialisation du contexte du jdc
186          self.condition_context=self.g_context.copy()
187
188          # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
189          # le contexte initial (--> permet d'évaluer un JDC en récupérant un contexte
190          # d'un autre par exemple)
191          if self.context_ini :
192             self.g_context.update(self.context_ini)
193             # Update du dictionnaire des concepts
194             for sdnom,sd in self.context_ini.items():
195                if isinstance(sd,ASSD):self.sds_dict[sdnom]=sd
196
197          if self.appli != None :
198             self.appli.affiche_infos('Interprétation du fichier de commandes en cours ...')
199          # On sauve le contexte pour garder la memoire des constantes
200          # En mode edition (EFICAS) ou lors des verifications le contexte
201          # est recalculé
202          # mais les constantes sont perdues
203          self.const_context=self.g_context
204          message.debug(SUPERV, "pass")
205          exec self.proc_compile in self.g_context
206
207          CONTEXT.unset_current_step()
208          if self.appli != None : self.appli.affiche_infos('')
209
210       except EOFError:
211         # Exception utilise pour interrompre un jeu
212         # de commandes avant la fin
213         # Fonctionnement normal, ne doit pas etre considere comme une erreur
214         CONTEXT.unset_current_step()
215         self.affiche_fin_exec()
216         self.traiter_fin_exec('commande')
217
218       except AsException,e:
219         # une erreur a ete identifiee
220         if CONTEXT.debug :
221           traceback.print_exc()
222         # l'exception a été récupérée avant (où, comment ?),
223         # donc on cherche dans le texte
224         txt = str(e)
225         if txt.find('MemoryError') >= 0:
226            txt = MemoryErrorMsg
227         self.cr.exception(txt)
228         CONTEXT.unset_current_step()
229
230       except NameError,e:
231         etype, value, tb = sys.exc_info()
232         l= traceback.extract_tb(tb)
233         s= traceback.format_exception_only("Erreur de nom",e)[0][:-1]
234         msg = "erreur de syntaxe,  %s ligne %d" % (s,l[-1][1])
235         if CONTEXT.debug :
236           traceback.print_exc()
237         self.cr.exception(msg)
238         CONTEXT.unset_current_step()
239
240       except self.UserError,exc_val:
241         self.traiter_user_exception(exc_val)
242         CONTEXT.unset_current_step()
243         self.affiche_fin_exec()
244         self.traiter_fin_exec('commande')
245
246       except :
247         # erreur inattendue
248         # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
249         # (tuple de 3 éléments)
250         if CONTEXT.debug : traceback.print_exc()
251
252         exc_typ,exc_val,exc_fr=sys.exc_info()
253         l=traceback.format_exception(exc_typ,exc_val,exc_fr)
254         self.cr.exception("erreur non prevue et non traitee prevenir la maintenance "+
255                            self.nom+'\n'+ string.join(l))
256         del exc_typ,exc_val,exc_fr
257         CONTEXT.unset_current_step()
258
259    def affiche_fin_exec(self):
260        """
261           Cette methode realise l'affichage final des statistiques de temps
262           apres l'execution de toutes
263           les commandes en mode commande par commande ou par lot
264           Elle doit etre surchargee pour en introduire un
265        """
266        return
267
268    def traiter_fin_exec(self,mode,etape=None):
269        """
270           Cette methode realise un traitement final apres l'execution de toutes
271           les commandes en mode commande par commande ou par lot
272           Par defaut il n'y a pas de traitement. Elle doit etre surchargee
273           pour en introduire un
274        """
275        message.info(SUPERV, "FIN D'EXECUTION %s %s", mode, etape)
276
277    def traiter_user_exception(self,exc_val):
278        """Cette methode realise un traitement sur les exceptions utilisateur
279           Par defaut il n'y a pas de traitement. La méthode doit etre
280           surchargée pour en introduire un.
281        """
282        return
283
284    def register(self,etape):
285       """
286          Cette méthode ajoute etape dans la liste des etapes : self.etapes
287          et retourne un numéro d'enregistrement
288       """
289       self.etapes.append(etape)
290       self.index_etapes[etape] = len(self.etapes) - 1
291       message.debug(SUPERV, "#%d %s", self.index_etapes[etape], etape.nom)
292       return self.g_register(etape)
293
294    def o_register(self,sd):
295       """
296          Retourne un identificateur pour concept
297       """
298       self.nsd=self.nsd+1
299       nom=sd.idracine + self.SEP + `self.nsd`
300       return nom
301
302    def g_register(self,etape):
303       """
304           Retourne un identificateur pour etape
305       """
306       self.nstep=self.nstep+1
307       idetape=etape.idracine + self.SEP + `self.nstep`
308       return idetape
309
310    def create_sdprod(self,etape,nomsd):
311       """
312           Cette methode doit fabriquer le concept produit retourne
313           par l'etape etape et le nommer.
314
315           Elle est appelée à l'initiative de l'etape
316           pendant le processus de construction de cette etape :
317           methode __call__ de la classe CMD (OPER ou MACRO)
318
319           Ce travail est réalisé par le contexte supérieur
320           (etape.parent) car dans certains cas, le concept ne doit
321           pas etre fabriqué mais l'etape doit simplement utiliser
322           un concept préexistant.
323
324           Deux cas possibles :
325                   - Cas 1 : etape.reuse != None : le concept est réutilisé
326                   - Cas 2 : l'étape appartient à une macro qui a déclaré un
327                           concept de sortie qui doit etre produit par cette
328                           etape.
329           Dans le cas du JDC, le deuxième cas ne peut pas se produire.
330       """
331       sd= etape.get_sd_prod()
332       if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None) :
333          # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
334          # d un concept. Commande non reentrante ou reuse absent.
335          self.NommerSdprod(sd,nomsd)
336       return sd
337
338    def NommerSdprod(self,sd,sdnom,restrict='non'):
339       """
340           Nomme la SD apres avoir verifie que le nommage est possible : nom
341           non utilise
342           Si le nom est deja utilise, leve une exception
343           Met le concept créé dans le concept global g_context
344       """
345       o=self.sds_dict.get(sdnom,None)
346       if isinstance(o,ASSD):
347          raise AsException("Nom de concept deja defini : %s" % sdnom)
348       if self._reserved_kw.get(sdnom) == 1:
349          raise AsException("Nom de concept invalide. '%s' est un mot-clé réservé." % sdnom)
350
351       # ATTENTION : Il ne faut pas ajouter sd dans sds car il s y trouve deja.
352       # Ajoute a la creation (appel de reg_sd).
353       self.sds_dict[sdnom]=sd
354       sd.set_name(sdnom)
355
356       # En plus si restrict vaut 'non', on insere le concept dans le contexte du JDC
357       if restrict == 'non':
358          self.g_context[sdnom]=sd
359          message.debug(SUPERV, "g_context[%r] = %s", sdnom, sd)
360
361    def reg_sd(self,sd):
362       """
363           Methode appelee dans l __init__ d un ASSD lors de sa creation
364           pour s enregistrer
365       """
366       self.sds.append(sd)
367       return self.o_register(sd)
368
369    def delete_concept_after_etape(self,etape,sd):
370       """
371           Met à jour les étapes du JDC qui sont après etape suite à
372           la disparition du concept sd
373       """
374       # Cette methode est définie dans le noyau mais ne sert que pendant
375       # la phase de creation des etapes et des concepts. Il n'y a aucun
376       # traitement particulier à réaliser.
377       # Dans d'autres conditions, il faut surcharger cette méthode
378       return
379
380    def supprime(self):
381       N_OBJECT.OBJECT.supprime(self)
382       for etape in self.etapes:
383          etape.supprime()
384
385    def get_file(self,unite=None,fic_origine=''):
386       """
387           Retourne le nom du fichier correspondant à un numero d'unité
388           logique (entier) ainsi que le source contenu dans le fichier
389       """
390       if self.appli :
391          # Si le JDC est relié à une application maitre, on délègue la recherche
392          file,text= self.appli.get_file(unite,fic_origine)
393       else:
394          file = None
395          if unite != None:
396             if os.path.exists("fort."+str(unite)):
397                file= "fort."+str(unite)
398          if file == None :
399             raise AsException("Impossible de trouver le fichier correspondant"
400                                " a l unite %s" % unite)
401          if not os.path.exists(file):
402             raise AsException("%s n'est pas un fichier existant" % unite)
403          fproc=open(file,'r')
404          text=fproc.read()
405          fproc.close()
406       if file == None : return None,None
407       text=string.replace(text,'\r\n','\n')
408       linecache.cache[file]=0,0,string.split(text,'\n'),file
409       return file,text
410
411    def set_par_lot(self,par_lot):
412       """
413           Met le mode de traitement a PAR LOT
414           ou a COMMANDE par COMMANDE
415           en fonction de la valeur du mot cle PAR_LOT et
416           du contexte : application maitre ou pas
417       """
418       if self.appli == None:
419         # Pas d application maitre
420         self.par_lot=par_lot
421       else:
422         # Avec application maitre
423         self.par_lot='OUI'
424
425    def accept(self,visitor):
426       """
427          Cette methode permet de parcourir l'arborescence des objets
428          en utilisant le pattern VISITEUR
429       """
430       visitor.visitJDC(self)
431
432    def interact(self):
433       """
434           Cette methode a pour fonction d'ouvrir un interpreteur
435           pour que l'utilisateur entre des commandes interactivement
436       """
437       CONTEXT.set_current_step(self)
438       try:
439          # Le module nommage utilise le module linecache pour accéder
440          # au source des commandes du jeu de commandes.
441          # Dans le cas d'un fichier, on accède au contenu de ce fichier
442          # Dans le cas de la console interactive, il faut pouvoir accéder
443          # aux commandes qui sont dans le buffer de la console
444          import linecache,code
445          console= code.InteractiveConsole(self.g_context,filename="<console>")
446          linecache.cache["<console>"]=0,0,console.buffer,"<console>"
447          banner="""***********************************************
448 *          Interpreteur interactif %s
449 ***********************************************""" % self.code
450          console.interact(banner)
451       finally:
452          console=None
453          CONTEXT.unset_current_step()
454
455    def get_contexte_avant(self,etape):
456       """
457          Retourne le dictionnaire des concepts connus avant etape
458          On tient compte des commandes qui modifient le contexte
459          comme DETRUIRE ou les macros
460          Si etape == None, on retourne le contexte en fin de JDC
461       """
462       # L'étape courante pour laquelle le contexte a été calculé est
463       # mémorisée dans self.index_etape_courante
464       # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
465       # mémorisant l'étape
466       # courante pendant le processus de construction des étapes.
467       # Si on insère des commandes (par ex, dans EFICAS), il faut préalablement
468       # remettre ce pointeur à 0
469       if etape:
470          index_etape = self.index_etapes[etape]
471       else:
472          index_etape=len(self.etapes)
473       if index_etape >= self.index_etape_courante:
474          # On calcule le contexte en partant du contexte existant
475          d=self.current_context
476          if self.index_etape_courante==0 and self.context_ini:
477             d.update(self.context_ini)
478          liste_etapes=self.etapes[self.index_etape_courante:index_etape]
479       else:
480          d=self.current_context={}
481          if self.context_ini:
482             d.update(self.context_ini)
483          liste_etapes=self.etapes
484
485       for e in liste_etapes:
486          if e is etape:
487             break
488          if e.isactif():
489             e.update_context(d)
490       self.index_etape_courante=index_etape
491       return d
492
493    def get_global_contexte(self):
494       """Retourne "un" contexte global ;-)"""
495       # N'est utilisé que par INCLUDE (sauf erreur).
496       # g_context est remis à {} en PAR_LOT='OUI'. const_context permet
497       # de retrouver ce qui y a été mis par exec_compile.
498       # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutés
499       # par get_global_contexte de la MACRO.
500       d = self.const_context.copy()
501       d.update(self.g_context)
502       return d
503
504
505    def get_contexte_courant(self, etape_courante=None):
506       """
507          Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
508          de construction) au moment de l'exécution de l'étape courante.
509       """
510       if etape_courante is None:
511          etape_courante = CONTEXT.get_current_step()
512       return self.get_contexte_avant(etape_courante)
513
514
515    def get_concept(self, nomsd):
516       """
517           Méthode pour recuperer un concept à partir de son nom
518       """
519       return self.get_contexte_courant().get(nomsd.strip(), None)
520
521    def del_concept(self, nomsd):
522       """
523          Méthode pour supprimer la référence d'un concept dans le sds_dict.
524          Ne détruire pas le concept (différent de supprime).
525       """
526       try:
527          del self.sds_dict[nomsd.strip()]
528       except:
529          pass
530
531
532    def get_cmd(self,nomcmd):
533       """
534           Méthode pour recuperer la definition d'une commande
535           donnee par son nom dans les catalogues declares
536           au niveau du jdc
537       """
538       for cata in self.cata:
539           if hasattr(cata,nomcmd):
540              return getattr(cata,nomcmd)
541
542    def append_reset(self,etape):
543        """
544           Ajoute une etape provenant d'un autre jdc a la liste des etapes
545           et remet à jour la parenté de l'étape et des concepts
546        """
547        self.etapes.append(etape)
548        self.index_etapes[etape] = len(self.etapes) - 1
549        etape.reparent(self)
550        etape.reset_jdc(self)
551
552    def sd_accessible(self):
553       """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
554       """
555       if CONTEXT.debug: print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
556       return self.par_lot == 'NON'
557
558
559    def _build_reserved_kw_list(self):
560        """Construit la liste des mots-clés réservés (interdits pour le
561        nommage des concepts)."""
562        wrk = set()
563        for cat in self.cata:
564            wrk.update([kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
565        wrk.difference_update(['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
566                               'GEOM', 'MCSIMP', 'MCFACT'])
567        self._reserved_kw = {}.fromkeys(wrk, 1)
568