1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2013 EDF R&D
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.
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.
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
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 Ce module contient la classe JDC qui sert à interpréter un jeu de commandes
27 import os,string,traceback
28 import types,sys,linecache
33 from N_Exception import AsException
34 from N_ASSD import ASSD
35 from N_info import message, SUPERV
38 MemoryErrorMsg = """MemoryError :
40 En général, cette erreur se produit car la mémoire utilisée hors du fortran
41 (jeveux) est importante.
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,
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.
59 class JDC(N_OBJECT.OBJECT):
61 Cette classe interprete un jeu de commandes fourni sous
62 la forme d'une chaine de caractères
66 Attributs d'instance :
78 from N_utils import SEP
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
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
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.
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)
106 self.par_lot_user = None
108 self.regles=definition.regles
109 self.code = definition.code
114 # Creation de l objet compte rendu pour collecte des erreurs
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)
125 self.index_etapes = {}
127 self.current_context={}
128 self.condition_context={}
129 self.index_etape_courante=0
130 self.UserError="UserError"
132 # permet transitoirement de conserver la liste des étapes
133 self.hist_etape = False
137 Cette methode compile la chaine procedure
138 Si des erreurs se produisent, elles sont consignées dans le
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 = """
154 - offset too large : liste trop longue derrière un mot-clé.
155 Solution : liste = (valeurs, ..., )
158 l=traceback.format_exception_only(SystemError,e)
159 l.append(erreurs_connues)
160 self.cr.exception("Compilation impossible : " + ''.join(l))
163 def exec_compile(self):
165 Cette méthode execute le jeu de commandes compilé dans le contexte
166 self.g_context de l'objet JDC
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
175 linecache.cache[self.nom]=0,0,string.split(self.procedure,'\n'),self.nom
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()))
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()
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
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
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()))
209 CONTEXT.unset_current_step()
210 if self.appli != None : self.appli.affiche_infos('')
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')
220 except AsException,e:
221 # une erreur a ete identifiee
223 traceback.print_exc()
224 # l'exception a été récupérée avant (où, comment ?),
225 # donc on cherche dans le texte
227 if txt.find('MemoryError') >= 0:
229 self.cr.exception(txt)
230 CONTEXT.unset_current_step()
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])
238 traceback.print_exc()
239 self.cr.exception(msg)
240 CONTEXT.unset_current_step()
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')
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()
254 traceback.print_exc()
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()
262 def affiche_fin_exec(self):
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
271 def traiter_fin_exec(self,mode,etape=None):
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
278 message.info(SUPERV, "FIN D'EXECUTION %s %s", mode, etape)
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.
287 def register(self,etape):
289 Cette méthode ajoute etape dans la liste des etapes : self.etapes
290 et retourne un numéro d'enregistrement
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)
297 def o_register(self,sd):
299 Retourne un identificateur pour concept
302 nom=sd.idracine + self.SEP + `self.nsd`
305 def g_register(self,etape):
307 Retourne un identificateur pour etape
309 self.nstep=self.nstep+1
310 idetape=etape.idracine + self.SEP + `self.nstep`
313 def create_sdprod(self,etape,nomsd):
315 Cette methode doit fabriquer le concept produit retourne
316 par l'etape etape et le nommer.
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)
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.
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
332 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
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)
342 def NommerSdprod(self,sd,sdnom,restrict='non'):
344 Nomme la SD apres avoir verifie que le nommage est possible : nom
346 Si le nom est deja utilise, leve une exception
347 Met le concept créé dans le concept global g_context
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)
356 # Ajoute a la creation (appel de reg_sd).
357 self.sds_dict[sdnom]=sd
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)
367 Methode appelee dans l __init__ d un ASSD lors de sa creation
370 return self.o_register(sd)
372 def delete_concept_after_etape(self,etape,sd):
374 Met à jour les étapes du JDC qui sont après etape suite à
375 la disparition du concept sd
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
384 N_OBJECT.OBJECT.supprime(self)
385 for etape in self.etapes:
388 def clean(self, netapes):
389 """Nettoie les `netapes` dernières étapes de la liste des étapes."""
392 for i in xrange(netapes):
399 #message.debug(SUPERV, "JDC.clean - etape = %r - refcount(e) = %d",
400 #e.nom, sys.getrefcount(e))
401 del self.index_etapes[e]
404 def get_file(self, unite=None, fic_origine='', fname=None):
406 Retourne le nom du fichier correspondant à un numero d'unité
407 logique (entier) ainsi que le source contenu dans le fichier
410 # Si le JDC est relié à une application maitre, on délègue la recherche
411 return self.appli.get_file(unite, fic_origine)
414 if os.path.exists("fort."+str(unite)):
415 fname= "fort."+str(unite)
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')
424 text = text.replace('\r\n', '\n')
425 linecache.cache[fname] = 0, 0, text.split('\n'), fname
428 def set_par_lot(self, par_lot, user_value=False):
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
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`).
441 #message.debug(SUPERV, "set par_lot = %r", par_lot)
443 self.par_lot_user = par_lot
444 if self.appli == None:
445 # Pas d application maitre
448 # Avec application maitre
451 def accept(self,visitor):
453 Cette methode permet de parcourir l'arborescence des objets
454 en utilisant le pattern VISITEUR
456 visitor.visitJDC(self)
460 Cette methode a pour fonction d'ouvrir un interpreteur
461 pour que l'utilisateur entre des commandes interactivement
463 CONTEXT.set_current_step(self)
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)
479 CONTEXT.unset_current_step()
481 def get_contexte_avant(self,etape):
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
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
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()
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)
508 from warnings import warn
509 warn("concept '%s' absent du contexte de %s" % (nom, self.nom),
510 RuntimeWarning, stacklevel=2)
513 index_etape = self.index_etapes[etape]
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]
523 d=self.current_context={}
525 d.update(self.context_ini)
526 liste_etapes=self.etapes
528 for e in liste_etapes:
533 self.index_etape_courante=index_etape
534 #message.debug(SUPERV, "returns : %s", [k for k, v in d.items() if isinstance(v, ASSD)])
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)
549 def get_contexte_courant(self, etape_courante=None):
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.
554 if etape_courante is None:
555 etape_courante = CONTEXT.get_current_step()
556 return self.get_contexte_avant(etape_courante)
559 def get_concept(self, nomsd):
561 Méthode pour récuperer un concept à partir de son nom
563 co = self.get_contexte_courant().get(nomsd.strip(), None)
564 if not isinstance(co, ASSD):
568 def get_concept_by_type(self, nomsd, typesd, etape):
570 Méthode pour récuperer un concept à partir de son nom et de son type.
571 Il aura comme père 'etape'.
573 assert issubclass(typesd, ASSD), typesd
574 co = typesd(etape=etape)
579 def del_concept(self, nomsd):
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).
585 del self.sds_dict[nomsd.strip()]
590 def get_cmd(self,nomcmd):
592 Méthode pour recuperer la definition d'une commande
593 donnee par son nom dans les catalogues declares
596 for cata in self.cata:
597 if hasattr(cata,nomcmd):
598 return getattr(cata,nomcmd)
600 def append_reset(self,etape):
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
605 self.etapes.append(etape)
606 self.index_etapes[etape] = len(self.etapes) - 1
608 etape.reset_jdc(self)
610 def sd_accessible(self):
611 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
613 if CONTEXT.debug: print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
614 return self.par_lot == 'NON'
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'])