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
22 Ce module contient la classe JDC qui sert à interpréter un jeu de commandes
36 from N_Exception import AsException, InterruptParsingError
37 from N_ASSD import ASSD
38 from N_info import message, SUPERV
39 from strfunc import get_encoding
41 from Extensions.i18n import tr
46 MemoryErrorMsg = """MemoryError :
48 En général, cette erreur se produit car la mémoire utilisée hors du fortran
49 (jeveux) est importante.
52 - le calcul produit de gros objets Python dans une macro-commande ou
53 dans le jeu de commande lui-même,
54 - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
55 qui a besoin de mémoire hors jeveux,
56 - utilisation de jeveux dynamique,
60 - distinguer la mémoire limite du calcul (case "Mémoire totale" de astk)
61 de la mémoire réservée à jeveux (case "dont Aster"), le reste étant
62 disponible pour les allocations dynamiques.
66 class JDC(N_OBJECT.OBJECT):
69 Cette classe interprete un jeu de commandes fourni sous
70 la forme d'une chaine de caractères
74 Attributs d'instance :
86 from N_utils import SEP
88 def __init__(self, definition=None, procedure=None, cata=None,
89 cata_ord_dico=None, parent=None,
90 nom='SansNom', appli=None, context_ini=None, **args):
91 self.procedure = procedure
92 self.definition = definition
94 if type(self.cata) != types.TupleType and cata != None:
95 self.cata = (self.cata,)
96 self._build_reserved_kw_list()
97 self.cata_ordonne_dico = cata_ord_dico
101 self.context_ini = context_ini
102 # On conserve les arguments supplémentaires. Il est possible de passer
103 # des informations globales au JDC par ce moyen. Il pourrait etre plus
104 # sur de mettre en place le mecanisme des mots-cles pour verifier la
105 # validité des valeurs passées.
107 # On initialise avec les parametres de la definition puis on
108 # update avec ceux du JDC
109 self.args = self.definition.args
110 self.args.update(args)
114 self.par_lot_user = None
116 self.regles = definition.regles
117 self.code = definition.code
122 # Creation de l objet compte rendu pour collecte des erreurs
124 self.cr = self.CR(debut="CR phase d'initialisation",
125 fin="fin CR phase d'initialisation")
126 # on met le jdc lui-meme dans le context global pour l'avoir sous
127 # l'etiquette "jdc" dans le fichier de commandes
128 self.g_context = {'jdc': self}
129 # message.debug(SUPERV, "g_context : %s - %s", self.g_context, id(self.g_context))
130 # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par
134 self.index_etapes = {}
136 self.current_context = {}
137 self.condition_context = {}
138 self.index_etape_courante = 0
139 self.UserError = "UserError"
141 # permet transitoirement de conserver la liste des étapes
142 self.hist_etape = False
146 Cette methode compile la chaine procedure
147 Si des erreurs se produisent, elles sont consignées dans le
151 if self.appli != None:
152 self.appli.affiche_infos(
153 'Compilation du fichier de commandes en cours ...')
154 # Python 2.7 compile function does not accept unicode filename, so we encode it
155 # with the current locale encoding in order to have a correct
157 encoded_filename = self.nom.encode(get_encoding())
158 self.proc_compile = compile(
159 self.procedure, encoded_filename, 'exec')
160 except SyntaxError, e:
162 traceback.print_exc()
163 l = traceback.format_exception_only(SyntaxError, e)
164 self.cr.exception("Compilation impossible : " + string.join(l))
165 except MemoryError, e:
166 self.cr.exception(MemoryErrorMsg)
167 except SystemError, e:
168 erreurs_connues = """
170 - offset too large : liste trop longue derrière un mot-clé.
171 Solution : liste = (valeurs, ..., )
174 l = traceback.format_exception_only(SystemError, e)
175 l.append(erreurs_connues)
176 self.cr.exception("Compilation impossible : " + ''.join(l))
179 def exec_compile(self):
181 Cette méthode execute le jeu de commandes compilé dans le contexte
182 self.g_context de l'objet JDC
184 CONTEXT.set_current_step(self)
185 # Le module nommage utilise le module linecache pour accéder
186 # au source des commandes du jeu de commandes.
187 # Dans le cas d'un fichier, on accède au contenu de ce fichier
188 # Dans le cas d'une chaine de caractères il faut accéder
189 # aux commandes qui sont dans la chaine
191 linecache.cache[self.nom] = 0, 0, string.split(
192 self.procedure, '\n'), self.nom
194 exec self.exec_init in self.g_context
195 # message.debug(SUPERV, "JDC.exec_compile_1 - len(g_context) = %d",
196 # len(self.g_context.keys()))
197 for obj_cata in self.cata:
198 if type(obj_cata) == types.ModuleType:
199 init2 = "from " + obj_cata.__name__ + " import *"
200 exec init2 in self.g_context
201 # message.debug(SUPERV, "JDC.exec_compile_2 - len(g_context) = %d",
202 # len(self.g_context.keys()))
204 # Initialisation du contexte global pour l'évaluation des conditions de BLOC
205 # On utilise une copie de l'initialisation du contexte du jdc
206 self.condition_context = self.g_context.copy()
208 # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
209 # le contexte initial (--> permet d'évaluer un JDC en récupérant un contexte
210 # d'un autre par exemple)
212 self.g_context.update(self.context_ini)
213 # Update du dictionnaire des concepts
214 for sdnom, sd in self.context_ini.items():
215 if isinstance(sd, ASSD):
216 self.sds_dict[sdnom] = sd
218 if self.appli != None:
219 self.appli.affiche_infos(
220 'Interprétation du fichier de commandes en cours ...')
221 # On sauve le contexte pour garder la memoire des constantes
222 # En mode edition (EFICAS) ou lors des verifications le contexte
224 # mais les constantes sont perdues
225 self.const_context = self.g_context
226 # message.debug(SUPERV, "pass")
227 exec self.proc_compile in self.g_context
228 # message.debug(SUPERV, "JDC.exec_compile_3 - len(g_context) = %d",
229 # len(self.g_context.keys()))
231 CONTEXT.unset_current_step()
232 if self.appli != None:
233 self.appli.affiche_infos('')
235 except InterruptParsingError:
236 # interrupt the command file parsing used by FIN to ignore the end
241 # Exception utilise pour interrompre un jeu
242 # de commandes avant la fin
243 # Fonctionnement normal, ne doit pas etre considere comme une
245 CONTEXT.unset_current_step()
246 self.affiche_fin_exec()
247 self.traiter_fin_exec('commande')
249 except AsException, e:
250 # une erreur a ete identifiee
252 traceback.print_exc()
253 # l'exception a été récupérée avant (où, comment ?),
254 # donc on cherche dans le texte
256 if txt.find('MemoryError') >= 0:
258 self.cr.exception(txt)
259 CONTEXT.unset_current_step()
262 etype, value, tb = sys.exc_info()
263 l = traceback.extract_tb(tb)
264 s = traceback.format_exception_only("Erreur de nom", e)[0][:-1]
265 msg = "erreur de syntaxe, %s ligne %d" % (s, l[-1][1])
267 traceback.print_exc()
268 self.cr.exception(msg)
269 CONTEXT.unset_current_step()
271 except self.UserError, exc_val:
272 self.traiter_user_exception(exc_val)
273 CONTEXT.unset_current_step()
274 self.affiche_fin_exec()
275 self.traiter_fin_exec('commande')
279 # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
280 # (tuple de 3 éléments)
282 traceback.print_exc()
284 traceback.print_exc()
286 exc_typ, exc_val, exc_fr = sys.exc_info()
287 l = traceback.format_exception(exc_typ, exc_val, exc_fr)
289 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + string.join(l))
290 del exc_typ, exc_val, exc_fr
291 CONTEXT.unset_current_step()
293 def affiche_fin_exec(self):
295 Cette methode realise l'affichage final des statistiques de temps
296 apres l'execution de toutes
297 les commandes en mode commande par commande ou par lot
298 Elle doit etre surchargee pour en introduire un
302 def traiter_fin_exec(self, mode, etape=None):
304 Cette methode realise un traitement final apres l'execution de toutes
305 les commandes en mode commande par commande ou par lot
306 Par defaut il n'y a pas de traitement. Elle doit etre surchargee
307 pour en introduire un
309 message.info(SUPERV, "FIN D'EXECUTION %s %s", mode, etape)
311 def traiter_user_exception(self, exc_val):
312 """Cette methode realise un traitement sur les exceptions utilisateur
313 Par defaut il n'y a pas de traitement. La méthode doit etre
314 surchargée pour en introduire un.
318 def register(self, etape):
320 Cette méthode ajoute etape dans la liste des etapes : self.etapes
321 et retourne un numéro d'enregistrement
323 self.etapes.append(etape)
324 self.index_etapes[etape] = len(self.etapes) - 1
325 # message.debug(SUPERV, "#%d %s", self.index_etapes[etape], etape.nom)
326 return self.g_register(etape)
328 def o_register(self, sd):
330 Retourne un identificateur pour concept
332 self.nsd = self.nsd + 1
333 nom = sd.idracine + self.SEP + `self.nsd`
336 def g_register(self, etape):
338 Retourne un identificateur pour etape
340 self.nstep = self.nstep + 1
341 idetape = etape.idracine + self.SEP + `self.nstep`
344 def create_sdprod(self, etape, nomsd):
346 Cette methode doit fabriquer le concept produit retourne
347 par l'etape etape et le nommer.
349 Elle est appelée à l'initiative de l'etape
350 pendant le processus de construction de cette etape :
351 methode __call__ de la classe CMD (OPER ou MACRO)
353 Ce travail est réalisé par le contexte supérieur
354 (etape.parent) car dans certains cas, le concept ne doit
355 pas etre fabriqué mais l'etape doit simplement utiliser
356 un concept préexistant.
359 - Cas 1 : etape.reuse != None : le concept est réutilisé
360 - Cas 2 : l'étape appartient à une macro qui a déclaré un
361 concept de sortie qui doit etre produit par cette
363 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
365 sd = etape.get_sd_prod()
366 if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
367 # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
368 # d un concept. Commande non reentrante ou reuse absent.
369 self.NommerSdprod(sd, nomsd)
372 def NommerSdprod(self, sd, sdnom, restrict='non'):
374 Nomme la SD apres avoir verifie que le nommage est possible : nom
376 Si le nom est deja utilise, leve une exception
377 Met le concept créé dans le concept global g_context
379 o = self.sds_dict.get(sdnom, None)
380 if isinstance(o, ASSD):
381 raise AsException(tr("Nom de concept deja defini : %s" % sdnom))
382 if sdnom in self._reserved_kw:
384 tr( "Nom de concept invalide. '%s' est un mot-clé réservé." % sdnom))
386 # Ajoute a la creation (appel de reg_sd).
387 self.sds_dict[sdnom] = sd
390 # En plus si restrict vaut 'non', on insere le concept dans le contexte
392 if restrict == 'non':
393 self.g_context[sdnom] = sd
394 # message.debug(SUPERV, "g_context[%r] = %s", sdnom, sd)
396 def reg_sd(self, sd):
398 Methode appelee dans l __init__ d un ASSD lors de sa creation
401 return self.o_register(sd)
403 def delete_concept_after_etape(self, etape, sd):
405 Met à jour les étapes du JDC qui sont après etape suite à
406 la disparition du concept sd
408 # Cette methode est définie dans le noyau mais ne sert que pendant
409 # la phase de creation des etapes et des concepts. Il n'y a aucun
410 # traitement particulier à réaliser.
411 # Dans d'autres conditions, il faut surcharger cette méthode
415 N_OBJECT.OBJECT.supprime(self)
416 for etape in self.etapes:
419 def clean(self, netapes):
420 """Nettoie les `netapes` dernières étapes de la liste des étapes."""
423 for i in xrange(netapes):
424 e = self.etapes.pop()
430 # message.debug(SUPERV, "JDC.clean - etape = %r - refcount(e) = %d",
431 # e.nom, sys.getrefcount(e))
432 del self.index_etapes[e]
434 def get_file(self, unite=None, fic_origine='', fname=None):
436 Retourne le nom du fichier correspondant à un numero d'unité
437 logique (entier) ainsi que le source contenu dans le fichier
440 # Si le JDC est relié à une application maitre, on délègue la
442 return self.appli.get_file(unite, fic_origine)
445 if os.path.exists("fort." + str(unite)):
446 fname = "fort." + str(unite)
448 raise AsException(tr("Impossible de trouver le fichier correspondant"
449 " a l unite %s" % unite))
450 if not os.path.exists(fname):
451 raise AsException (tr("%s n'est pas un fichier existant" % fname))
452 fproc = open(fname, 'r')
455 text = text.replace('\r\n', '\n')
456 linecache.cache[fname] = 0, 0, text.split('\n'), fname
459 def set_par_lot(self, par_lot, user_value=False):
461 Met le mode de traitement a PAR LOT
462 ou a COMMANDE par COMMANDE
463 en fonction de la valeur du mot cle PAR_LOT et
464 du contexte : application maitre ou pas
466 En PAR_LOT='NON', il n'y a pas d'ambiguité.
467 En PAR_LOT='OUI', E_SUPERV positionne l'attribut à 'NON' après la phase
468 d'analyse et juste avant la phase d'exécution.
469 `user_value` : permet de stocker la valeur choisie par l'utilisateur
470 pour l'interroger plus tard (par exemple dans `get_contexte_avant`).
472 # message.debug(SUPERV, "set par_lot = %r", par_lot)
474 self.par_lot_user = par_lot
475 if self.appli == None:
476 # Pas d application maitre
477 self.par_lot = par_lot
479 # Avec application maitre
482 def accept(self, visitor):
484 Cette methode permet de parcourir l'arborescence des objets
485 en utilisant le pattern VISITEUR
487 visitor.visitJDC(self)
491 Cette methode a pour fonction d'ouvrir un interpreteur
492 pour que l'utilisateur entre des commandes interactivement
494 CONTEXT.set_current_step(self)
496 # Le module nommage utilise le module linecache pour accéder
497 # au source des commandes du jeu de commandes.
498 # Dans le cas d'un fichier, on accède au contenu de ce fichier
499 # Dans le cas de la console interactive, il faut pouvoir accéder
500 # aux commandes qui sont dans le buffer de la console
503 console = code.InteractiveConsole(
504 self.g_context, filename="<console>")
505 linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
506 banner = """***********************************************
507 * Interpreteur interactif %s
508 ***********************************************""" % self.code
509 console.interact(banner)
512 CONTEXT.unset_current_step()
514 def get_contexte_avant(self, etape):
516 Retourne le dictionnaire des concepts connus avant etape
517 On tient compte des commandes qui modifient le contexte
518 comme DETRUIRE ou les macros
519 Si etape == None, on retourne le contexte en fin de JDC
521 # L'étape courante pour laquelle le contexte a été calculé est
522 # mémorisée dans self.index_etape_courante
523 # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
525 # courante pendant le processus de construction des étapes.
526 # Si on insère des commandes (par ex, dans EFICAS), il faut préalablement
527 # remettre ce pointeur à 0
528 # message.debug(SUPERV, "g_context : %s", [k for k, v in self.g_context.items() if isinstance(v, ASSD)])
529 # message.debug(SUPERV, "current_context : %s", [k for k, v in
530 # self.current_context.items() if isinstance(v, ASSD)])
531 if self.par_lot_user == 'NON':
532 d = self.current_context = self.g_context.copy()
535 # retirer les sd produites par 'etape'
536 sd_names = [sd.nom for sd in etape.get_created_sd()]
537 # message.debug(SUPERV, "reuse : %s, sdprods : %s", etape.reuse,
543 from warnings import warn
545 tr("concept '%s' absent du contexte de %s" % (
547 RuntimeWarning, stacklevel=2)
550 index_etape = self.index_etapes[etape]
552 index_etape = len(self.etapes)
553 if index_etape >= self.index_etape_courante:
554 # On calcule le contexte en partant du contexte existant
555 d = self.current_context
556 if self.index_etape_courante == 0 and self.context_ini:
557 d.update(self.context_ini)
558 liste_etapes = self.etapes[self.index_etape_courante:index_etape]
560 d = self.current_context = {}
562 d.update(self.context_ini)
563 liste_etapes = self.etapes
565 for e in liste_etapes:
570 self.index_etape_courante = index_etape
571 # message.debug(SUPERV, "returns : %s", [k for k, v in d.items() if
572 # isinstance(v, ASSD)])
575 def get_global_contexte(self):
576 """Retourne "un" contexte global ;-)"""
577 # N'est utilisé que par INCLUDE (sauf erreur).
578 # g_context est remis à {} en PAR_LOT='OUI'. const_context permet
579 # de retrouver ce qui y a été mis par exec_compile.
580 # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutés
581 # par get_global_contexte de la MACRO.
582 d = self.const_context.copy()
583 d.update(self.g_context)
586 def get_contexte_courant(self, etape_courante=None):
588 Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
589 de construction) au moment de l'exécution de l'étape courante.
591 if etape_courante is None:
592 etape_courante = CONTEXT.get_current_step()
593 return self.get_contexte_avant(etape_courante)
595 def get_concept(self, nomsd):
597 Méthode pour récuperer un concept à partir de son nom
599 co = self.get_contexte_courant().get(nomsd.strip(), None)
600 if not isinstance(co, ASSD):
604 def get_concept_by_type(self, nomsd, typesd, etape):
606 Méthode pour récuperer un concept à partir de son nom et de son type.
607 Il aura comme père 'etape'.
609 assert issubclass(typesd, ASSD), typesd
610 co = typesd(etape=etape)
615 def del_concept(self, nomsd):
617 Méthode pour supprimer la référence d'un concept dans le sds_dict.
618 Ne détruire pas le concept (différent de supprime).
621 del self.sds_dict[nomsd.strip()]
625 def get_cmd(self, nomcmd):
627 Méthode pour recuperer la definition d'une commande
628 donnee par son nom dans les catalogues declares
631 for cata in self.cata:
632 if hasattr(cata, nomcmd):
633 return getattr(cata, nomcmd)
635 def append_reset(self, etape):
637 Ajoute une etape provenant d'un autre jdc a la liste des etapes
638 et remet à jour la parenté de l'étape et des concepts
640 self.etapes.append(etape)
641 self.index_etapes[etape] = len(self.etapes) - 1
643 etape.reset_jdc(self)
645 def sd_accessible(self):
646 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
649 print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
650 return self.par_lot == 'NON'
652 def _build_reserved_kw_list(self):
653 """Construit la liste des mots-clés réservés (interdits pour le
654 nommage des concepts)."""
655 self._reserved_kw = set()
656 for cat in self.cata:
657 self._reserved_kw.update(
658 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
659 self._reserved_kw.difference_update(
660 ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
661 'GEOM', 'MCSIMP', 'MCFACT'])