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 a interpreter un jeu de commandes
26 from __future__ import absolute_import
27 from __future__ import print_function
29 from builtins import str
30 from builtins import range
39 from . import N_OBJECT
41 from .N_Exception import AsException, InterruptParsingError
42 from .N_ASSD import ASSD
43 from .strfunc import get_encoding
44 from six.moves import range
47 MemoryErrorMsg = """MemoryError :
49 En general, cette erreur se produit car la memoire utilisee hors du fortran
50 (jeveux) est importante.
53 - le calcul produit de gros objets Python dans une macro-commande ou
54 dans le jeu de commande lui-meme,
55 - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
56 qui a besoin de memoire hors jeveux,
57 - utilisation de jeveux dynamique,
61 - distinguer la memoire limite du calcul (case "Memoire totale" de astk)
62 de la memoire reservee a jeveux (case "dont Aster"), le reste etant
63 disponible pour les allocations dynamiques.
67 class JDC(N_OBJECT.OBJECT):
70 Cette classe interprete un jeu de commandes fourni sous
71 la forme d'une chaine de caractères
75 Attributs d'instance :
87 from .N_utils import SEP
89 def __init__(self, definition=None, procedure=None, cata=None,
90 cata_ord_dico=None, parent=None,
91 nom='SansNom', appli=None, context_ini=None, **args):
92 self.procedure = procedure
93 self.definition = definition
95 if type(self.cata) != tuple and cata != None:
96 self.cata = (self.cata,)
97 self._build_reserved_kw_list()
98 self.cata_ordonne_dico = cata_ord_dico
102 self.context_ini = context_ini
103 # On conserve les arguments supplementaires. Il est possible de passer
104 # des informations globales au JDC par ce moyen. Il pourrait etre plus
105 # sur de mettre en place le mecanisme des mots-cles pour verifier la
106 # validite des valeurs passees.
108 # On initialise avec les parametres de la definition puis on
109 # update avec ceux du JDC
110 self.args = self.definition.args
111 self.args.update(args)
115 self.par_lot_user = None
117 self.regles = definition.regles
118 self.code = definition.code
123 # Creation de l objet compte rendu pour collecte des erreurs
125 self.cr = self.CR(debut="CR phase d'initialisation",
126 fin="fin CR phase d'initialisation")
127 # on met le jdc lui-meme dans le context global pour l'avoir sous
128 # l'etiquette "jdc" dans le fichier de commandes
129 self.g_context = {'jdc': self}
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 etapes
142 self.hist_etape = False
146 Cette methode compile la chaine procedure
147 Si des erreurs se produisent, elles sont consignees 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 as e:
162 traceback.print_exc()
163 l = traceback.format_exception_only(SyntaxError, e)
164 self.cr.exception("Compilation impossible : " + ''.join(l))
165 except MemoryError as e:
166 self.cr.exception(MemoryErrorMsg)
167 except SystemError as e:
168 erreurs_connues = """
170 - offset too large : liste trop longue derrière un mot-cle.
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 methode execute le jeu de commandes compile 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 acceder
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 acceder
189 # aux commandes qui sont dans la chaine
191 linecache.cache[self.nom] = 0, 0, self.procedure.split('\n'), self.nom
193 exec(self.exec_init, self.g_context)
194 for obj_cata in self.cata:
195 if type(obj_cata) == types.ModuleType:
196 init2 = "from " + obj_cata.__name__ + " import *"
197 exec(init2, self.g_context)
199 # Initialisation du contexte global pour l'evaluation des conditions de BLOC
200 # On utilise une copie de l'initialisation du contexte du jdc
201 self.condition_context = self.g_context.copy()
203 # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
204 # le contexte initial (--> permet d'evaluer un JDC en recuperant un contexte
205 # d'un autre par exemple)
207 self.g_context.update(self.context_ini)
208 # Update du dictionnaire des concepts
209 for sdnom, sd in list(self.context_ini.items()):
210 if isinstance(sd, ASSD):
211 self.sds_dict[sdnom] = sd
213 if self.appli != None:
214 self.appli.affiche_infos(
215 'Interpretation du fichier de commandes en cours ...')
216 # On sauve le contexte pour garder la memoire des constantes
217 # En mode edition (EFICAS) ou lors des verifications le contexte
219 # mais les constantes sont perdues
220 self.const_context = self.g_context
221 exec(self.proc_compile, self.g_context)
223 CONTEXT.unset_current_step()
224 if self.appli != None:
225 self.appli.affiche_infos('')
227 except InterruptParsingError:
228 # interrupt the command file parsing used by FIN to ignore the end
233 # Exception utilise pour interrompre un jeu
234 # de commandes avant la fin
235 # Fonctionnement normal, ne doit pas etre considere comme une
237 CONTEXT.unset_current_step()
238 self.affiche_fin_exec()
239 self.traiter_fin_exec('commande')
241 except AsException as e:
242 # une erreur a ete identifiee
244 traceback.print_exc()
245 # l'exception a ete recuperee avant (ou, comment ?),
246 # donc on cherche dans le texte
248 if txt.find('MemoryError') >= 0:
250 self.cr.exception(txt)
251 CONTEXT.unset_current_step()
253 except NameError as e:
254 etype, value, tb = sys.exc_info()
255 l = traceback.extract_tb(tb)
256 s = traceback.format_exception_only("Erreur de nom", e)[0][:-1]
257 msg = "erreur de syntaxe, %s ligne %d" % (s, l[-1][1])
259 traceback.print_exc()
260 self.cr.exception(msg)
261 CONTEXT.unset_current_step()
263 # except self.UserError as exc_val:
264 # self.traiter_user_exception(exc_val)
265 # CONTEXT.unset_current_step()
266 # self.affiche_fin_exec()
267 # self.traiter_fin_exec('commande')
271 # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
272 # (tuple de 3 elements)
274 traceback.print_exc()
276 traceback.print_exc()
278 exc_typ, exc_val, exc_fr = sys.exc_info()
279 l = traceback.format_exception(exc_typ, exc_val, exc_fr)
281 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + ''.join(l))
282 del exc_typ, exc_val, exc_fr
283 CONTEXT.unset_current_step()
285 def affiche_fin_exec(self):
287 Cette methode realise l'affichage final des statistiques de temps
288 apres l'execution de toutes
289 les commandes en mode commande par commande ou par lot
290 Elle doit etre surchargee pour en introduire un
294 def traiter_fin_exec(self, mode, etape=None):
296 Cette methode realise un traitement final apres l'execution de toutes
297 les commandes en mode commande par commande ou par lot
298 Par defaut il n'y a pas de traitement. Elle doit etre surchargee
299 pour en introduire un
301 print ( "FIN D'EXECUTION %s %s" %s( mode, etape))
303 def traiter_user_exception(self, exc_val):
304 """Cette methode realise un traitement sur les exceptions utilisateur
305 Par defaut il n'y a pas de traitement. La methode doit etre
306 surchargee pour en introduire un.
310 def register(self, etape):
312 Cette methode ajoute etape dans la liste des etapes : self.etapes
313 et retourne un numero d'enregistrement
315 self.etapes.append(etape)
316 self.index_etapes[etape] = len(self.etapes) - 1
317 return self.g_register(etape)
319 def o_register(self, sd):
321 Retourne un identificateur pour concept
323 self.nsd = self.nsd + 1
324 nom = sd.idracine + self.SEP + repr(self.nsd)
327 def g_register(self, etape):
329 Retourne un identificateur pour etape
331 self.nstep = self.nstep + 1
332 idetape = etape.idracine + self.SEP + repr(self.nstep)
335 def create_sdprod(self, etape, nomsd):
337 Cette methode doit fabriquer le concept produit retourne
338 par l'etape etape et le nommer.
340 Elle est appelee a l'initiative de l'etape
341 pendant le processus de construction de cette etape :
342 methode __call__ de la classe CMD (OPER ou MACRO)
344 Ce travail est realise par le contexte superieur
345 (etape.parent) car dans certains cas, le concept ne doit
346 pas etre fabrique mais l'etape doit simplement utiliser
347 un concept preexistant.
350 - Cas 1 : etape.reuse != None : le concept est reutilise
351 - Cas 2 : l'etape appartient a une macro qui a declare un
352 concept de sortie qui doit etre produit par cette
354 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
356 sd = etape.get_sd_prod()
357 if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
358 # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
359 # d un concept. Commande non reentrante ou reuse absent.
360 self.NommerSdprod(sd, nomsd)
363 def NommerSdprod(self, sd, sdnom, restrict='non'):
365 Nomme la SD apres avoir verifie que le nommage est possible : nom
367 Si le nom est deja utilise, leve une exception
368 Met le concept cree dans le concept global g_context
370 o = self.sds_dict.get(sdnom, None)
371 if isinstance(o, ASSD):
372 raise AsException("Nom de concept deja defini : %s" % sdnom)
373 if sdnom in self._reserved_kw:
375 "Nom de concept invalide. '%s' est un mot-cle reserve." % sdnom)
377 # Ajoute a la creation (appel de reg_sd).
378 self.sds_dict[sdnom] = sd
381 # En plus si restrict vaut 'non', on insere le concept dans le contexte
383 if restrict == 'non':
384 self.g_context[sdnom] = sd
386 def reg_sd(self, sd):
388 Methode appelee dans l __init__ d un ASSD lors de sa creation
391 return self.o_register(sd)
393 def delete_concept_after_etape(self, etape, sd):
395 Met a jour les etapes du JDC qui sont après etape suite a
396 la disparition du concept sd
398 # Cette methode est definie dans le noyau mais ne sert que pendant
399 # la phase de creation des etapes et des concepts. Il n'y a aucun
400 # traitement particulier a realiser.
401 # Dans d'autres conditions, il faut surcharger cette methode
405 N_OBJECT.OBJECT.supprime(self)
406 for etape in self.etapes:
409 def clean(self, netapes):
410 """Nettoie les `netapes` dernières etapes de la liste des etapes."""
413 for i in range(netapes):
414 e = self.etapes.pop()
420 del self.index_etapes[e]
422 def get_file(self, unite=None, fic_origine='', fname=None):
424 Retourne le nom du fichier correspondant a un numero d'unite
425 logique (entier) ainsi que le source contenu dans le fichier
428 # Si le JDC est relie a une application maitre, on delègue la
430 return self.appli.get_file(unite, fic_origine)
433 if os.path.exists("fort." + str(unite)):
434 fname = "fort." + str(unite)
436 raise AsException("Impossible de trouver le fichier correspondant"
437 " a l unite %s" % unite)
438 if not os.path.exists(fname):
439 raise AsException("%s n'est pas un fichier existant" % fname)
440 fproc = open(fname, 'r')
443 text = text.replace('\r\n', '\n')
444 linecache.cache[fname] = 0, 0, text.split('\n'), fname
447 def set_par_lot(self, par_lot, user_value=False):
449 Met le mode de traitement a PAR LOT
450 ou a COMMANDE par COMMANDE
451 en fonction de la valeur du mot cle PAR_LOT et
452 du contexte : application maitre ou pas
454 En PAR_LOT='NON', il n'y a pas d'ambiguite.
455 d'analyse et juste avant la phase d'execution.
456 `user_value` : permet de stocker la valeur choisie par l'utilisateur
457 pour l'interroger plus tard (par exemple dans `get_contexte_avant`).
460 self.par_lot_user = par_lot
461 if self.appli == None:
462 # Pas d application maitre
463 self.par_lot = par_lot
465 # Avec application maitre
468 def accept(self, visitor):
470 Cette methode permet de parcourir l'arborescence des objets
471 en utilisant le pattern VISITEUR
473 visitor.visitJDC(self)
477 Cette methode a pour fonction d'ouvrir un interpreteur
478 pour que l'utilisateur entre des commandes interactivement
480 CONTEXT.set_current_step(self)
482 # Le module nommage utilise le module linecache pour acceder
483 # au source des commandes du jeu de commandes.
484 # Dans le cas d'un fichier, on accède au contenu de ce fichier
485 # Dans le cas de la console interactive, il faut pouvoir acceder
486 # aux commandes qui sont dans le buffer de la console
489 console = code.InteractiveConsole(
490 self.g_context, filename="<console>")
491 linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
492 banner = """***********************************************
493 * Interpreteur interactif %s
494 ***********************************************""" % self.code
495 console.interact(banner)
498 CONTEXT.unset_current_step()
500 def get_contexte_avant(self, etape):
502 Retourne le dictionnaire des concepts connus avant etape
503 On tient compte des commandes qui modifient le contexte
504 comme DETRUIRE ou les macros
505 Si etape == None, on retourne le contexte en fin de JDC
507 # L'etape courante pour laquelle le contexte a ete calcule est
508 # memorisee dans self.index_etape_courante
509 # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
511 # courante pendant le processus de construction des etapes.
512 # Si on insère des commandes (par ex, dans EFICAS), il faut prealablement
513 # remettre ce pointeur a 0
514 # self.current_context.items() if isinstance(v, ASSD)])
515 if self.par_lot_user == 'NON':
516 d = self.current_context = self.g_context.copy()
519 # retirer les sd produites par 'etape'
520 sd_names = [sd.nom for sd in etape.get_created_sd()]
525 from warnings import warn
527 "concept '%s' absent du contexte de %s" % (
529 RuntimeWarning, stacklevel=2)
532 index_etape = self.index_etapes[etape]
534 index_etape = len(self.etapes)
535 if index_etape >= self.index_etape_courante:
536 # On calcule le contexte en partant du contexte existant
537 d = self.current_context
538 if self.index_etape_courante == 0 and self.context_ini:
539 d.update(self.context_ini)
540 liste_etapes = self.etapes[self.index_etape_courante:index_etape]
542 d = self.current_context = {}
544 d.update(self.context_ini)
545 liste_etapes = self.etapes
547 for e in liste_etapes:
552 self.index_etape_courante = index_etape
555 def get_global_contexte(self):
556 """Retourne "un" contexte global ;-)"""
557 # N'est utilise que par INCLUDE (sauf erreur).
558 # g_context est remis a {} en PAR_LOT='OUI'. const_context permet
559 # de retrouver ce qui y a ete mis par exec_compile.
560 # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutes
561 # par get_global_contexte de la MACRO.
562 d = self.const_context.copy()
563 d.update(self.g_context)
566 def get_contexte_courant(self, etape_courante=None):
568 Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
569 de construction) au moment de l'execution de l'etape courante.
571 if etape_courante is None:
572 etape_courante = CONTEXT.get_current_step()
573 return self.get_contexte_avant(etape_courante)
575 def get_concept(self, nomsd):
577 Methode pour recuperer un concept a partir de son nom
579 co = self.get_contexte_courant().get(nomsd.strip(), None)
580 if not isinstance(co, ASSD):
584 def get_concept_by_type(self, nomsd, typesd, etape):
586 Methode pour recuperer un concept a partir de son nom et de son type.
587 Il aura comme père 'etape'.
589 assert issubclass(typesd, ASSD), typesd
590 co = typesd(etape=etape)
595 def del_concept(self, nomsd):
597 Methode pour supprimer la reference d'un concept dans le sds_dict.
598 Ne detruire pas le concept (different de supprime).
601 del self.sds_dict[nomsd.strip()]
605 def get_cmd(self, nomcmd):
607 Methode pour recuperer la definition d'une commande
608 donnee par son nom dans les catalogues declares
611 for cata in self.cata:
612 if hasattr(cata, nomcmd):
613 return getattr(cata, nomcmd)
615 def append_reset(self, etape):
617 Ajoute une etape provenant d'un autre jdc a la liste des etapes
618 et remet a jour la parente de l'etape et des concepts
620 self.etapes.append(etape)
621 self.index_etapes[etape] = len(self.etapes) - 1
623 etape.reset_jdc(self)
625 def sd_accessible(self):
626 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
629 print((' `- JDC sd_accessible : PAR_LOT =', self.par_lot))
630 return self.par_lot == 'NON'
632 def _build_reserved_kw_list(self):
633 """Construit la liste des mots-cles reserves (interdits pour le
634 nommage des concepts)."""
635 self._reserved_kw = set()
636 for cat in self.cata:
637 self._reserved_kw.update(
638 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
639 self._reserved_kw.difference_update(
640 ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
641 'GEOM', 'MCSIMP', 'MCFACT'])