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
42 MemoryErrorMsg = """MemoryError :
44 En général, cette erreur se produit car la mémoire utilisée hors du fortran
45 (jeveux) est importante.
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,
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.
62 class JDC(N_OBJECT.OBJECT):
65 Cette classe interprete un jeu de commandes fourni sous
66 la forme d'une chaine de caractères
70 Attributs d'instance :
82 from N_utils import SEP
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
90 if type(self.cata) != types.TupleType and cata != None:
91 self.cata = (self.cata,)
92 self._build_reserved_kw_list()
93 self.cata_ordonne_dico = cata_ord_dico
97 self.context_ini = context_ini
98 # On conserve les arguments supplémentaires. Il est possible de passer
99 # des informations globales au JDC par ce moyen. Il pourrait etre plus
100 # sur de mettre en place le mecanisme des mots-cles pour verifier la
101 # validité des valeurs passées.
103 # On initialise avec les parametres de la definition puis on
104 # update avec ceux du JDC
105 self.args = self.definition.args
106 self.args.update(args)
110 self.par_lot_user = None
112 self.regles = definition.regles
113 self.code = definition.code
118 # Creation de l objet compte rendu pour collecte des erreurs
120 self.cr = self.CR(debut="CR phase d'initialisation",
121 fin="fin CR phase d'initialisation")
122 # on met le jdc lui-meme dans le context global pour l'avoir sous
123 # l'etiquette "jdc" dans le fichier de commandes
124 self.g_context = {'jdc': self}
125 # message.debug(SUPERV, "g_context : %s - %s", self.g_context, id(self.g_context))
126 # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par
130 self.index_etapes = {}
132 self.current_context = {}
133 self.condition_context = {}
134 self.index_etape_courante = 0
135 self.UserError = "UserError"
137 # permet transitoirement de conserver la liste des étapes
138 self.hist_etape = False
142 Cette methode compile la chaine procedure
143 Si des erreurs se produisent, elles sont consignées dans le
147 if self.appli != None:
148 self.appli.affiche_infos(
149 'Compilation du fichier de commandes en cours ...')
150 # Python 2.7 compile function does not accept unicode filename, so we encode it
151 # with the current locale encoding in order to have a correct
153 encoded_filename = self.nom.encode(get_encoding())
154 self.proc_compile = compile(
155 self.procedure, encoded_filename, 'exec')
156 except SyntaxError, e:
158 traceback.print_exc()
159 l = traceback.format_exception_only(SyntaxError, e)
160 self.cr.exception("Compilation impossible : " + string.join(l))
161 except MemoryError, e:
162 self.cr.exception(MemoryErrorMsg)
163 except SystemError, e:
164 erreurs_connues = """
166 - offset too large : liste trop longue derrière un mot-clé.
167 Solution : liste = (valeurs, ..., )
170 l = traceback.format_exception_only(SystemError, e)
171 l.append(erreurs_connues)
172 self.cr.exception("Compilation impossible : " + ''.join(l))
175 def exec_compile(self):
177 Cette méthode execute le jeu de commandes compilé dans le contexte
178 self.g_context de l'objet JDC
180 CONTEXT.set_current_step(self)
181 # Le module nommage utilise le module linecache pour accéder
182 # au source des commandes du jeu de commandes.
183 # Dans le cas d'un fichier, on accède au contenu de ce fichier
184 # Dans le cas d'une chaine de caractères il faut accéder
185 # aux commandes qui sont dans la chaine
187 linecache.cache[self.nom] = 0, 0, string.split(
188 self.procedure, '\n'), self.nom
190 exec self.exec_init in self.g_context
191 # message.debug(SUPERV, "JDC.exec_compile_1 - len(g_context) = %d",
192 # len(self.g_context.keys()))
193 for obj_cata in self.cata:
194 if type(obj_cata) == types.ModuleType:
195 init2 = "from " + obj_cata.__name__ + " import *"
196 exec init2 in self.g_context
197 # message.debug(SUPERV, "JDC.exec_compile_2 - len(g_context) = %d",
198 # len(self.g_context.keys()))
200 # Initialisation du contexte global pour l'évaluation des conditions de BLOC
201 # On utilise une copie de l'initialisation du contexte du jdc
202 self.condition_context = self.g_context.copy()
204 # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
205 # le contexte initial (--> permet d'évaluer un JDC en récupérant un contexte
206 # d'un autre par exemple)
208 self.g_context.update(self.context_ini)
209 # Update du dictionnaire des concepts
210 for sdnom, sd in self.context_ini.items():
211 if isinstance(sd, ASSD):
212 self.sds_dict[sdnom] = sd
214 if self.appli != None:
215 self.appli.affiche_infos(
216 'Interprétation du fichier de commandes en cours ...')
217 # On sauve le contexte pour garder la memoire des constantes
218 # En mode edition (EFICAS) ou lors des verifications le contexte
220 # mais les constantes sont perdues
221 self.const_context = self.g_context
222 # message.debug(SUPERV, "pass")
223 exec self.proc_compile in self.g_context
224 # message.debug(SUPERV, "JDC.exec_compile_3 - len(g_context) = %d",
225 # len(self.g_context.keys()))
227 CONTEXT.unset_current_step()
228 if self.appli != None:
229 self.appli.affiche_infos('')
231 except InterruptParsingError:
232 # interrupt the command file parsing used by FIN to ignore the end
237 # Exception utilise pour interrompre un jeu
238 # de commandes avant la fin
239 # Fonctionnement normal, ne doit pas etre considere comme une
241 CONTEXT.unset_current_step()
242 self.affiche_fin_exec()
243 self.traiter_fin_exec('commande')
245 except AsException, e:
246 # une erreur a ete identifiee
248 traceback.print_exc()
249 # l'exception a été récupérée avant (où, comment ?),
250 # donc on cherche dans le texte
252 if txt.find('MemoryError') >= 0:
254 self.cr.exception(txt)
255 CONTEXT.unset_current_step()
258 etype, value, tb = sys.exc_info()
259 l = traceback.extract_tb(tb)
260 s = traceback.format_exception_only("Erreur de nom", e)[0][:-1]
261 msg = "erreur de syntaxe, %s ligne %d" % (s, l[-1][1])
263 traceback.print_exc()
264 self.cr.exception(msg)
265 CONTEXT.unset_current_step()
267 except self.UserError, exc_val:
268 self.traiter_user_exception(exc_val)
269 CONTEXT.unset_current_step()
270 self.affiche_fin_exec()
271 self.traiter_fin_exec('commande')
275 # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
276 # (tuple de 3 éléments)
278 traceback.print_exc()
280 traceback.print_exc()
282 exc_typ, exc_val, exc_fr = sys.exc_info()
283 l = traceback.format_exception(exc_typ, exc_val, exc_fr)
285 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + string.join(l))
286 del exc_typ, exc_val, exc_fr
287 CONTEXT.unset_current_step()
289 def affiche_fin_exec(self):
291 Cette methode realise l'affichage final des statistiques de temps
292 apres l'execution de toutes
293 les commandes en mode commande par commande ou par lot
294 Elle doit etre surchargee pour en introduire un
298 def traiter_fin_exec(self, mode, etape=None):
300 Cette methode realise un traitement final apres l'execution de toutes
301 les commandes en mode commande par commande ou par lot
302 Par defaut il n'y a pas de traitement. Elle doit etre surchargee
303 pour en introduire un
305 message.info(SUPERV, "FIN D'EXECUTION %s %s", mode, etape)
307 def traiter_user_exception(self, exc_val):
308 """Cette methode realise un traitement sur les exceptions utilisateur
309 Par defaut il n'y a pas de traitement. La méthode doit etre
310 surchargée pour en introduire un.
314 def register(self, etape):
316 Cette méthode ajoute etape dans la liste des etapes : self.etapes
317 et retourne un numéro d'enregistrement
319 self.etapes.append(etape)
320 self.index_etapes[etape] = len(self.etapes) - 1
321 # message.debug(SUPERV, "#%d %s", self.index_etapes[etape], etape.nom)
322 return self.g_register(etape)
324 def o_register(self, sd):
326 Retourne un identificateur pour concept
328 self.nsd = self.nsd + 1
329 nom = sd.idracine + self.SEP + `self.nsd`
332 def g_register(self, etape):
334 Retourne un identificateur pour etape
336 self.nstep = self.nstep + 1
337 idetape = etape.idracine + self.SEP + `self.nstep`
340 def create_sdprod(self, etape, nomsd):
342 Cette methode doit fabriquer le concept produit retourne
343 par l'etape etape et le nommer.
345 Elle est appelée à l'initiative de l'etape
346 pendant le processus de construction de cette etape :
347 methode __call__ de la classe CMD (OPER ou MACRO)
349 Ce travail est réalisé par le contexte supérieur
350 (etape.parent) car dans certains cas, le concept ne doit
351 pas etre fabriqué mais l'etape doit simplement utiliser
352 un concept préexistant.
355 - Cas 1 : etape.reuse != None : le concept est réutilisé
356 - Cas 2 : l'étape appartient à une macro qui a déclaré un
357 concept de sortie qui doit etre produit par cette
359 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
361 sd = etape.get_sd_prod()
362 if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
363 # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
364 # d un concept. Commande non reentrante ou reuse absent.
365 self.NommerSdprod(sd, nomsd)
368 def NommerSdprod(self, sd, sdnom, restrict='non'):
370 Nomme la SD apres avoir verifie que le nommage est possible : nom
372 Si le nom est deja utilise, leve une exception
373 Met le concept créé dans le concept global g_context
375 o = self.sds_dict.get(sdnom, None)
376 if isinstance(o, ASSD):
377 raise AsException("Nom de concept deja defini : %s" % sdnom)
378 if sdnom in self._reserved_kw:
380 "Nom de concept invalide. '%s' est un mot-clé réservé." % sdnom)
382 # Ajoute a la creation (appel de reg_sd).
383 self.sds_dict[sdnom] = sd
386 # En plus si restrict vaut 'non', on insere le concept dans le contexte
388 if restrict == 'non':
389 self.g_context[sdnom] = sd
390 # message.debug(SUPERV, "g_context[%r] = %s", sdnom, sd)
392 def reg_sd(self, sd):
394 Methode appelee dans l __init__ d un ASSD lors de sa creation
397 return self.o_register(sd)
399 def delete_concept_after_etape(self, etape, sd):
401 Met à jour les étapes du JDC qui sont après etape suite à
402 la disparition du concept sd
404 # Cette methode est définie dans le noyau mais ne sert que pendant
405 # la phase de creation des etapes et des concepts. Il n'y a aucun
406 # traitement particulier à réaliser.
407 # Dans d'autres conditions, il faut surcharger cette méthode
411 N_OBJECT.OBJECT.supprime(self)
412 for etape in self.etapes:
415 def clean(self, netapes):
416 """Nettoie les `netapes` dernières étapes de la liste des étapes."""
419 for i in xrange(netapes):
420 e = self.etapes.pop()
426 # message.debug(SUPERV, "JDC.clean - etape = %r - refcount(e) = %d",
427 # e.nom, sys.getrefcount(e))
428 del self.index_etapes[e]
430 def get_file(self, unite=None, fic_origine='', fname=None):
432 Retourne le nom du fichier correspondant à un numero d'unité
433 logique (entier) ainsi que le source contenu dans le fichier
436 # Si le JDC est relié à une application maitre, on délègue la
438 return self.appli.get_file(unite, fic_origine)
441 if os.path.exists("fort." + str(unite)):
442 fname = "fort." + str(unite)
444 raise AsException("Impossible de trouver le fichier correspondant"
445 " a l unite %s" % unite)
446 if not os.path.exists(fname):
447 raise AsException("%s n'est pas un fichier existant" % fname)
448 fproc = open(fname, 'r')
451 text = text.replace('\r\n', '\n')
452 linecache.cache[fname] = 0, 0, text.split('\n'), fname
455 def set_par_lot(self, par_lot, user_value=False):
457 Met le mode de traitement a PAR LOT
458 ou a COMMANDE par COMMANDE
459 en fonction de la valeur du mot cle PAR_LOT et
460 du contexte : application maitre ou pas
462 En PAR_LOT='NON', il n'y a pas d'ambiguité.
463 En PAR_LOT='OUI', E_SUPERV positionne l'attribut à 'NON' après la phase
464 d'analyse et juste avant la phase d'exécution.
465 `user_value` : permet de stocker la valeur choisie par l'utilisateur
466 pour l'interroger plus tard (par exemple dans `get_contexte_avant`).
468 # message.debug(SUPERV, "set par_lot = %r", par_lot)
470 self.par_lot_user = par_lot
471 if self.appli == None:
472 # Pas d application maitre
473 self.par_lot = par_lot
475 # Avec application maitre
478 def accept(self, visitor):
480 Cette methode permet de parcourir l'arborescence des objets
481 en utilisant le pattern VISITEUR
483 visitor.visitJDC(self)
487 Cette methode a pour fonction d'ouvrir un interpreteur
488 pour que l'utilisateur entre des commandes interactivement
490 CONTEXT.set_current_step(self)
492 # Le module nommage utilise le module linecache pour accéder
493 # au source des commandes du jeu de commandes.
494 # Dans le cas d'un fichier, on accède au contenu de ce fichier
495 # Dans le cas de la console interactive, il faut pouvoir accéder
496 # aux commandes qui sont dans le buffer de la console
499 console = code.InteractiveConsole(
500 self.g_context, filename="<console>")
501 linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
502 banner = """***********************************************
503 * Interpreteur interactif %s
504 ***********************************************""" % self.code
505 console.interact(banner)
508 CONTEXT.unset_current_step()
510 def get_contexte_avant(self, etape):
512 Retourne le dictionnaire des concepts connus avant etape
513 On tient compte des commandes qui modifient le contexte
514 comme DETRUIRE ou les macros
515 Si etape == None, on retourne le contexte en fin de JDC
517 # L'étape courante pour laquelle le contexte a été calculé est
518 # mémorisée dans self.index_etape_courante
519 # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
521 # courante pendant le processus de construction des étapes.
522 # Si on insère des commandes (par ex, dans EFICAS), il faut préalablement
523 # remettre ce pointeur à 0
524 # message.debug(SUPERV, "g_context : %s", [k for k, v in self.g_context.items() if isinstance(v, ASSD)])
525 # message.debug(SUPERV, "current_context : %s", [k for k, v in
526 # self.current_context.items() if isinstance(v, ASSD)])
527 if self.par_lot_user == 'NON':
528 d = self.current_context = self.g_context.copy()
531 # retirer les sd produites par 'etape'
532 sd_names = [sd.nom for sd in etape.get_created_sd()]
533 # message.debug(SUPERV, "reuse : %s, sdprods : %s", etape.reuse,
539 from warnings import warn
541 "concept '%s' absent du contexte de %s" % (
543 RuntimeWarning, stacklevel=2)
546 index_etape = self.index_etapes[etape]
548 index_etape = len(self.etapes)
549 if index_etape >= self.index_etape_courante:
550 # On calcule le contexte en partant du contexte existant
551 d = self.current_context
552 if self.index_etape_courante == 0 and self.context_ini:
553 d.update(self.context_ini)
554 liste_etapes = self.etapes[self.index_etape_courante:index_etape]
556 d = self.current_context = {}
558 d.update(self.context_ini)
559 liste_etapes = self.etapes
561 for e in liste_etapes:
566 self.index_etape_courante = index_etape
567 # message.debug(SUPERV, "returns : %s", [k for k, v in d.items() if
568 # isinstance(v, ASSD)])
571 def get_global_contexte(self):
572 """Retourne "un" contexte global ;-)"""
573 # N'est utilisé que par INCLUDE (sauf erreur).
574 # g_context est remis à {} en PAR_LOT='OUI'. const_context permet
575 # de retrouver ce qui y a été mis par exec_compile.
576 # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutés
577 # par get_global_contexte de la MACRO.
578 d = self.const_context.copy()
579 d.update(self.g_context)
582 def get_contexte_courant(self, etape_courante=None):
584 Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
585 de construction) au moment de l'exécution de l'étape courante.
587 if etape_courante is None:
588 etape_courante = CONTEXT.get_current_step()
589 return self.get_contexte_avant(etape_courante)
591 def get_concept(self, nomsd):
593 Méthode pour récuperer un concept à partir de son nom
595 co = self.get_contexte_courant().get(nomsd.strip(), None)
596 if not isinstance(co, ASSD):
600 def get_concept_by_type(self, nomsd, typesd, etape):
602 Méthode pour récuperer un concept à partir de son nom et de son type.
603 Il aura comme père 'etape'.
605 assert issubclass(typesd, ASSD), typesd
606 co = typesd(etape=etape)
611 def del_concept(self, nomsd):
613 Méthode pour supprimer la référence d'un concept dans le sds_dict.
614 Ne détruire pas le concept (différent de supprime).
617 del self.sds_dict[nomsd.strip()]
621 def get_cmd(self, nomcmd):
623 Méthode pour recuperer la definition d'une commande
624 donnee par son nom dans les catalogues declares
627 for cata in self.cata:
628 if hasattr(cata, nomcmd):
629 return getattr(cata, nomcmd)
631 def append_reset(self, etape):
633 Ajoute une etape provenant d'un autre jdc a la liste des etapes
634 et remet à jour la parenté de l'étape et des concepts
636 self.etapes.append(etape)
637 self.index_etapes[etape] = len(self.etapes) - 1
639 etape.reset_jdc(self)
641 def sd_accessible(self):
642 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
645 print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
646 return self.par_lot == 'NON'
648 def _build_reserved_kw_list(self):
649 """Construit la liste des mots-clés réservés (interdits pour le
650 nommage des concepts)."""
651 self._reserved_kw = set()
652 for cat in self.cata:
653 self._reserved_kw.update(
654 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
655 self._reserved_kw.difference_update(
656 ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
657 'GEOM', 'MCSIMP', 'MCFACT'])