Salome HOME
94c462398a73f70f21f4c8d962467e91921ea3f5
[tools/eficas.git] / Noyau / N_JDC.py
1 #@ MODIF N_JDC Noyau  DATE 16/11/2009   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 - 2002  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
39
40
41
42 MemoryErrorMsg = """MemoryError :
43
44 En général, cette erreur se produit car la mémoire utilisée hors du fortran
45 (jeveux) est importante.
46
47 Causes possibles :
48    - le calcul produit de gros objets Python dans une macro-commande ou
49      dans le jeu de commande lui-même,
50    - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
51      qui a besoin de mémoire hors jeveux,
52    - utilisation de jeveux dynamique,
53    - ...
54
55 Solution :
56    - distinguer la mémoire limite du calcul (case "Mémoire totale" de astk)
57      de la mémoire réservée à jeveux (case "dont Aster"), le reste étant
58      disponible pour les allocations dynamiques.
59 """
60
61
62
63 class JDC(N_OBJECT.OBJECT):
64    """
65       Cette classe interprete un jeu de commandes fourni sous
66       la forme d'une chaine de caractères
67
68       Attributs de classe :
69
70       Attributs d'instance :
71
72    """
73    nature = "JDC"
74    CR=N_CR.CR
75    exec_init="""
76 import Accas
77 from Accas import _F
78 from Accas import *
79 NONE = None
80 """
81
82    from N_utils import SEP
83
84    def __init__(self,definition=None,procedure=None,cata=None,
85                      cata_ord_dico=None,parent=None,
86                      nom='SansNom',appli=None,context_ini=None,**args):
87       self.procedure=procedure
88       self.definition = definition
89       self.cata=cata
90       if type(self.cata) != types.TupleType and cata != None: 
91          self.cata=(self.cata,)
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('Interpretation 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          exec self.proc_compile in self.g_context
205
206          CONTEXT.unset_current_step()
207          if self.appli != None : self.appli.affiche_infos('')
208
209       except EOFError:
210         # Exception utilise pour interrompre un jeu
211         # de commandes avant la fin
212         # Fonctionnement normal, ne doit pas etre considere comme une erreur
213         CONTEXT.unset_current_step()
214         self.affiche_fin_exec()
215         self.traiter_fin_exec('commande')
216
217       except AsException,e:
218         # une erreur a ete identifiee
219         if CONTEXT.debug :
220           traceback.print_exc()
221         # l'exception a été récupérée avant (où, comment ?),
222         # donc on cherche dans le texte
223         txt = str(e)
224         if txt.find('MemoryError') >= 0:
225            txt = MemoryErrorMsg
226         self.cr.exception(txt)
227         CONTEXT.unset_current_step()
228
229       except NameError,e:
230         etype, value, tb = sys.exc_info()
231         l= traceback.extract_tb(tb)
232         s= traceback.format_exception_only("Erreur de nom",e)[0][:-1]
233         message = "erreur de syntaxe,  %s ligne %d" % (s,l[-1][1])
234         if CONTEXT.debug :
235           traceback.print_exc()
236         self.cr.exception(message)
237         CONTEXT.unset_current_step()
238
239       except self.UserError,exc_val:
240         self.traiter_user_exception(exc_val)
241         CONTEXT.unset_current_step()
242         self.affiche_fin_exec()
243         self.traiter_fin_exec('commande')
244     
245       except :
246         # erreur inattendue
247         # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info() 
248         # (tuple de 3 éléments)
249         if CONTEXT.debug : traceback.print_exc()
250
251         exc_typ,exc_val,exc_fr=sys.exc_info()
252         l=traceback.format_exception(exc_typ,exc_val,exc_fr)
253         self.cr.exception("erreur non prevue et non traitee prevenir la maintenance "+
254                            self.nom+'\n'+ string.join(l))
255         del exc_typ,exc_val,exc_fr
256         CONTEXT.unset_current_step()
257
258    def affiche_fin_exec(self):
259        """
260           Cette methode realise l'affichage final des statistiques de temps
261           apres l'execution de toutes
262           les commandes en mode commande par commande ou par lot
263           Elle doit etre surchargee pour en introduire un
264        """
265        return
266
267    def traiter_fin_exec(self,mode,etape=None):
268        """
269           Cette methode realise un traitement final apres l'execution de toutes
270           les commandes en mode commande par commande ou par lot
271           Par defaut il n'y a pas de traitement. Elle doit etre surchargee
272           pour en introduire un
273        """
274        print "FIN D'EXECUTION",mode,etape
275
276    def traiter_user_exception(self,exc_val):
277        """Cette methode realise un traitement sur les exceptions utilisateur    
278           Par defaut il n'y a pas de traitement. La méthode doit etre 
279           surchargée pour en introduire un.
280        """
281        return 
282
283    def register(self,etape):
284       """
285          Cette méthode ajoute etape dans la liste des etapes : self.etapes
286          et retourne un numéro d'enregistrement
287       """
288       self.etapes.append(etape)
289       self.index_etapes[etape] = len(self.etapes) - 1
290       return self.g_register(etape)
291
292    def o_register(self,sd):
293       """
294          Retourne un identificateur pour concept
295       """
296       self.nsd=self.nsd+1
297       nom=sd.idracine + self.SEP + `self.nsd`
298       return nom
299
300    def g_register(self,etape):
301       """
302           Retourne un identificateur pour etape
303       """
304       self.nstep=self.nstep+1
305       idetape=etape.idracine + self.SEP + `self.nstep`
306       return idetape
307
308    def create_sdprod(self,etape,nomsd):
309       """ 
310           Cette methode doit fabriquer le concept produit retourne
311           par l'etape etape et le nommer.
312
313           Elle est appelée à l'initiative de l'etape
314           pendant le processus de construction de cette etape : 
315           methode __call__ de la classe CMD (OPER ou MACRO)
316
317           Ce travail est réalisé par le contexte supérieur 
318           (etape.parent) car dans certains cas, le concept ne doit 
319           pas etre fabriqué mais l'etape doit simplement utiliser 
320           un concept préexistant.
321
322           Deux cas possibles :
323                   - Cas 1 : etape.reuse != None : le concept est réutilisé
324                   - Cas 2 : l'étape appartient à une macro qui a déclaré un 
325                           concept de sortie qui doit etre produit par cette 
326                           etape.
327           Dans le cas du JDC, le deuxième cas ne peut pas se produire.
328       """
329       sd= etape.get_sd_prod()
330       if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None) :
331          # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation 
332          # d un concept. Commande non reentrante ou reuse absent.
333          self.NommerSdprod(sd,nomsd)
334       return sd
335
336    def NommerSdprod(self,sd,sdnom,restrict='non'):
337       """ 
338           Nomme la SD apres avoir verifie que le nommage est possible : nom 
339           non utilise
340           Si le nom est deja utilise, leve une exception
341           Met le concept créé dans le concept global g_context
342       """
343       if CONTEXT.debug : print "JDC.NommerSdprod ",sd,sdnom
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
349       # ATTENTION : Il ne faut pas ajouter sd dans sds car il s y trouve deja.
350       # Ajoute a la creation (appel de reg_sd).
351       self.sds_dict[sdnom]=sd
352       sd.set_name(sdnom)
353
354       # En plus si restrict vaut 'non', on insere le concept dans le contexte du JDC
355       if restrict == 'non':
356          self.g_context[sdnom]=sd
357
358    def reg_sd(self,sd):
359       """ 
360           Methode appelee dans l __init__ d un ASSD lors de sa creation 
361           pour s enregistrer
362       """
363       self.sds.append(sd)
364       return self.o_register(sd)
365
366    def delete_concept_after_etape(self,etape,sd):
367       """
368           Met à jour les étapes du JDC qui sont après etape suite à
369           la disparition du concept sd
370       """
371       # Cette methode est définie dans le noyau mais ne sert que pendant 
372       # la phase de creation des etapes et des concepts. Il n'y a aucun 
373       # traitement particulier à réaliser.
374       # Dans d'autres conditions, il faut surcharger cette méthode
375       return
376
377    def supprime(self):
378       N_OBJECT.OBJECT.supprime(self)
379       for etape in self.etapes:
380          etape.supprime()
381
382    def get_file(self,unite=None,fic_origine=''):
383       """
384           Retourne le nom du fichier correspondant à un numero d'unité 
385           logique (entier) ainsi que le source contenu dans le fichier
386       """
387       if self.appli :
388          # Si le JDC est relié à une application maitre, on délègue la recherche
389          file,text= self.appli.get_file(unite,fic_origine)
390       else:
391          file = None
392          if unite != None:
393             if os.path.exists("fort."+str(unite)):
394                file= "fort."+str(unite)
395          if file == None :
396             raise AsException("Impossible de trouver le fichier correspondant"
397                                " a l unite %s" % unite)
398          if not os.path.exists(file):
399             raise AsException("%s n'est pas un fichier existant" % unite)
400          fproc=open(file,'r')
401          text=fproc.read()
402          fproc.close()
403       if file == None : return None,None
404       text=string.replace(text,'\r\n','\n')
405       linecache.cache[file]=0,0,string.split(text,'\n'),file
406       return file,text
407
408    def set_par_lot(self,par_lot):
409       """ 
410           Met le mode de traitement a PAR LOT 
411           ou a COMMANDE par COMMANDE
412           en fonction de la valeur du mot cle PAR_LOT et 
413           du contexte : application maitre ou pas
414       """
415       if self.appli == None:
416         # Pas d application maitre
417         self.par_lot=par_lot
418       else:
419         # Avec application maitre
420         self.par_lot='OUI'
421
422    def accept(self,visitor):
423       """
424          Cette methode permet de parcourir l'arborescence des objets
425          en utilisant le pattern VISITEUR
426       """
427       visitor.visitJDC(self)
428
429    def interact(self):
430       """
431           Cette methode a pour fonction d'ouvrir un interpreteur 
432           pour que l'utilisateur entre des commandes interactivement
433       """
434       CONTEXT.set_current_step(self)
435       try:
436          # Le module nommage utilise le module linecache pour accéder
437          # au source des commandes du jeu de commandes.
438          # Dans le cas d'un fichier, on accède au contenu de ce fichier
439          # Dans le cas de la console interactive, il faut pouvoir accéder
440          # aux commandes qui sont dans le buffer de la console
441          import linecache,code
442          console= code.InteractiveConsole(self.g_context,filename="<console>")
443          linecache.cache["<console>"]=0,0,console.buffer,"<console>"
444          banner="""***********************************************
445 *          Interpreteur interactif %s
446 ***********************************************""" % self.code
447          console.interact(banner)
448       finally:
449          console=None
450          CONTEXT.unset_current_step()
451
452    def get_contexte_avant(self,etape):
453       """
454          Retourne le dictionnaire des concepts connus avant etape
455          On tient compte des commandes qui modifient le contexte
456          comme DETRUIRE ou les macros
457          Si etape == None, on retourne le contexte en fin de JDC
458       """
459       # L'étape courante pour laquelle le contexte a été calculé est 
460       # mémorisée dans self.index_etape_courante
461       # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en 
462       # mémorisant l'étape
463       # courante pendant le processus de construction des étapes.
464       # Si on insère des commandes (par ex, dans EFICAS), il faut préalablement
465       # remettre ce pointeur à 0
466       if etape:
467          index_etape = self.index_etapes[etape]
468       else:
469          index_etape=len(self.etapes)
470       if index_etape >= self.index_etape_courante:
471          # On calcule le contexte en partant du contexte existant
472          d=self.current_context
473          if self.index_etape_courante==0 and self.context_ini:
474             d.update(self.context_ini)
475          liste_etapes=self.etapes[self.index_etape_courante:index_etape]
476       else:
477          d=self.current_context={}
478          if self.context_ini:
479             d.update(self.context_ini)
480          liste_etapes=self.etapes
481
482       for e in liste_etapes:
483          if e is etape:
484             break
485          if e.isactif():
486             e.update_context(d)
487       self.index_etape_courante=index_etape
488       return d
489
490    def get_global_contexte(self):
491       return self.g_context.copy()
492
493
494    def get_contexte_courant(self, etape_courante=None):
495       """
496          Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
497          de construction) au moment de l'exécution de l'étape courante.
498       """
499       if etape_courante is None:
500          etape_courante = CONTEXT.get_current_step()
501       return self.get_contexte_avant(etape_courante)
502
503
504    def get_concept(self, nomsd):
505       """
506           Méthode pour recuperer un concept à partir de son nom
507       """
508       return self.get_contexte_courant().get(nomsd.strip(), None)
509
510    def del_concept(self, nomsd):
511       """
512          Méthode pour supprimer la référence d'un concept dans le sds_dict.
513          Ne détruire pas le concept (différent de supprime).
514       """
515       try:
516          del self.sds_dict[nomsd.strip()]
517       except:
518          pass
519
520
521    def get_cmd(self,nomcmd):
522       """
523           Méthode pour recuperer la definition d'une commande
524           donnee par son nom dans les catalogues declares
525           au niveau du jdc
526       """
527       for cata in self.cata:
528           if hasattr(cata,nomcmd):
529              return getattr(cata,nomcmd)
530
531    def append_reset(self,etape):
532        """
533           Ajoute une etape provenant d'un autre jdc a la liste des etapes
534           et remet à jour la parenté de l'étape et des concepts
535        """
536        self.etapes.append(etape)
537        self.index_etapes[etape] = len(self.etapes) - 1
538        etape.reparent(self)
539        etape.reset_jdc(self)
540
541    def sd_accessible(self):
542       """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
543       """
544       if CONTEXT.debug: print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
545       return self.par_lot == 'NON'
546