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