2 # Copyright (C) 2007-2017 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 getEncoding
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
96 #if type(self.cata) != tuple and cata != None:
97 # self.cata = (self.cata,)
98 self._build_reserved_kw_list()
99 self.cata_ordonne_dico = cata_ord_dico
103 self.context_ini = context_ini
104 # On conserve les arguments supplementaires. Il est possible de passer
105 # des informations globales au JDC par ce moyen. Il pourrait etre plus
106 # sur de mettre en place le mecanisme des mots-cles pour verifier la
107 # validite des valeurs passees.
109 # On initialise avec les parametres de la definition puis on
110 # update avec ceux du JDC
111 self.args = self.definition.args
112 self.args.update(args)
116 self.parLot_user = None
118 self.regles = definition.regles
119 self.code = definition.code
124 # Creation de l objet compte rendu pour collecte des erreurs
126 self.cr = self.CR(debut="CR phase d'initialisation",
127 fin="fin CR phase d'initialisation")
128 # on met le jdc lui-meme dans le context global pour l'avoir sous
129 # l'etiquette "jdc" dans le fichier de commandes
130 self.g_context = {'jdc': self}
131 # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par
135 self.index_etapes = {}
137 self.current_context = {}
138 self.condition_context = {}
139 self.index_etape_courante = 0
140 self.UserError = "UserError"
142 # permet transitoirement de conserver la liste des etapes
143 self.hist_etape = False
147 Cette methode compile la chaine procedure
148 Si des erreurs se produisent, elles sont consignees dans le
152 if self.appli != None:
153 self.appli.afficheInfos(
154 'Compilation du fichier de commandes en cours ...')
155 # Python 2.7 compile function does not accept unicode filename, so we encode it
156 # with the current locale encoding in order to have a correct
158 encoded_filename = self.nom.encode(getEncoding())
159 self.proc_compile = compile(
160 self.procedure, encoded_filename, 'exec')
161 except SyntaxError as e:
163 traceback.print_exc()
164 l = traceback.format_exception_only(SyntaxError, e)
165 self.cr.exception("Compilation impossible : " + ''.join(l))
166 except MemoryError as e:
167 self.cr.exception(MemoryErrorMsg)
168 except SystemError as e:
169 erreurs_connues = """
171 - offset too large : liste trop longue derrière un mot-cle.
172 Solution : liste = (valeurs, ..., )
175 l = traceback.format_exception_only(SystemError, e)
176 l.append(erreurs_connues)
177 self.cr.exception("Compilation impossible : " + ''.join(l))
180 def setCurrentContext(self):
181 # beaucoup trop simple Ne tient pas compte des imports
183 # ne sert que pour le POC
184 CONTEXT.setCurrentStep(self)
186 def execCompile(self):
188 Cette methode execute le jeu de commandes compile dans le contexte
189 self.g_context de l'objet JDC
191 CONTEXT.setCurrentStep(self)
192 # Le module nommage utilise le module linecache pour acceder
193 # au source des commandes du jeu de commandes.
194 # Dans le cas d'un fichier, on accède au contenu de ce fichier
195 # Dans le cas d'une chaine de caractères il faut acceder
196 # aux commandes qui sont dans la chaine
198 linecache.cache[self.nom] = 0, 0, self.procedure.split('\n'), self.nom
200 exec(self.exec_init, self.g_context)
201 for obj_cata in (self.cata,):
202 if type(obj_cata) == types.ModuleType:
203 init2 = "from " + obj_cata.__name__ + " import *"
204 exec(init2, self.g_context)
206 # ici on a un catalogue en grammaire Eficas XML
207 # il faut ajouter ce qu on a construit au contexte
208 for (k,v) in obj_cata.contexteXML.items() :
210 # Initialisation du contexte global pour l'evaluation des conditions de BLOC
211 # On utilise une copie de l'initialisation du contexte du jdc
212 self.condition_context = self.g_context.copy()
214 # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
215 # le contexte initial (--> permet d'evaluer un JDC en recuperant un contexte
216 # d'un autre par exemple)
218 self.g_context.update(self.context_ini)
219 # Update du dictionnaire des concepts
220 for sdnom, sd in list(self.context_ini.items()):
221 if isinstance(sd, ASSD):
222 self.sds_dict[sdnom] = sd
224 if self.appli != None:
225 self.appli.afficheInfos(
226 'Interpretation du fichier de commandes en cours ...')
227 # On sauve le contexte pour garder la memoire des constantes
228 # En mode edition (EFICAS) ou lors des verifications le contexte
230 # mais les constantes sont perdues
231 self.const_context = self.g_context
232 exec(self.proc_compile, self.g_context)
234 CONTEXT.unsetCurrentStep()
235 if self.appli != None:
236 self.appli.afficheInfos('')
238 except InterruptParsingError:
239 # interrupt the command file parsing used by FIN to ignore the end
244 # Exception utilise pour interrompre un jeu
245 # de commandes avant la fin
246 # Fonctionnement normal, ne doit pas etre considere comme une
248 CONTEXT.unsetCurrentStep()
249 self.afficheFinExec()
250 self.traiterFinExec('commande')
252 except AsException as e:
253 # une erreur a ete identifiee
255 traceback.print_exc()
256 # l'exception a ete recuperee avant (ou, comment ?),
257 # donc on cherche dans le texte
259 if txt.find('MemoryError') >= 0:
261 self.cr.exception(txt)
262 CONTEXT.unsetCurrentStep()
264 except NameError as e:
265 etype, value, tb = sys.exc_info()
266 l = traceback.extract_tb(tb)
267 s = traceback.format_exception_only(NameError,e)
268 msg = "erreur de syntaxe, %s ligne %d" % (s, l[-1][1])
270 traceback.print_exc()
271 self.cr.exception(msg)
272 CONTEXT.unsetCurrentStep()
274 # except self.UserError as exc_val:
275 # self.traiterUserException(exc_val)
276 # CONTEXT.unsetCurrentStep()
277 # self.afficheFinExec()
278 # self.traiterFinExec('commande')
282 # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
283 # (tuple de 3 elements)
285 traceback.print_exc()
287 traceback.print_exc()
289 exc_typ, exc_val, exc_fr = sys.exc_info()
290 l = traceback.format_exception(exc_typ, exc_val, exc_fr)
292 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + ''.join(l))
293 del exc_typ, exc_val, exc_fr
294 CONTEXT.unsetCurrentStep()
296 def afficheFinExec(self):
298 Cette methode realise l'affichage final des statistiques de temps
299 apres l'execution de toutes
300 les commandes en mode commande par commande ou par lot
301 Elle doit etre surchargee pour en introduire un
305 def traiterFinExec(self, mode, etape=None):
307 Cette methode realise un traitement final apres l'execution de toutes
308 les commandes en mode commande par commande ou par lot
309 Par defaut il n'y a pas de traitement. Elle doit etre surchargee
310 pour en introduire un
312 print ( "FIN D'EXECUTION %s %s" %s( mode, etape))
314 def traiterUserException(self, exc_val):
315 """Cette methode realise un traitement sur les exceptions utilisateur
316 Par defaut il n'y a pas de traitement. La methode doit etre
317 surchargee pour en introduire un.
321 def register(self, etape):
323 Cette methode ajoute etape dans la liste des etapes : self.etapes
324 et retourne un numero d'enregistrement
326 self.etapes.append(etape)
327 self.index_etapes[etape] = len(self.etapes) - 1
328 return self.gRegister(etape)
330 def o_register(self, sd):
332 Retourne un identificateur pour concept
334 self.nsd = self.nsd + 1
335 nom = sd.idracine + self.SEP + repr(self.nsd)
338 def gRegister(self, etape):
340 Retourne un identificateur pour etape
342 self.nstep = self.nstep + 1
343 idetape = etape.idracine + self.SEP + repr(self.nstep)
346 def createSdprod(self, etape, nomsd):
348 Cette methode doit fabriquer le concept produit retourne
349 par l'etape etape et le nommer.
351 Elle est appelee a l'initiative de l'etape
352 pendant le processus de construction de cette etape :
353 methode __call__ de la classe CMD (OPER ou MACRO)
355 Ce travail est realise par le contexte superieur
356 (etape.parent) car dans certains cas, le concept ne doit
357 pas etre fabrique mais l'etape doit simplement utiliser
358 un concept preexistant.
361 - Cas 1 : etape.reuse != None : le concept est reutilise
362 - Cas 2 : l'etape appartient a une macro qui a declare un
363 concept de sortie qui doit etre produit par cette
365 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
367 sd = etape.getSdProd()
368 if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
369 # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
370 # d un concept. Commande non reentrante ou reuse absent.
371 self.NommerSdprod(sd, nomsd)
374 def NommerSdprod(self, sd, sdnom, restrict='non'):
376 Nomme la SD apres avoir verifie que le nommage est possible : nom
378 Si le nom est deja utilise, leve une exception
379 Met le concept cree dans le concept global g_context
381 o = self.sds_dict.get(sdnom, None)
382 if isinstance(o, ASSD):
383 raise AsException("Nom de concept deja defini : %s" % sdnom)
384 if sdnom in self._reserved_kw:
386 "Nom de concept invalide. '%s' est un mot-cle reserve." % sdnom)
388 # Ajoute a la creation (appel de regSD).
389 self.sds_dict[sdnom] = sd
392 # En plus si restrict vaut 'non', on insere le concept dans le contexte
394 if restrict == 'non':
395 self.g_context[sdnom] = sd
399 Methode appelee dans l __init__ d un ASSD lors de sa creation
402 return self.o_register(sd)
404 def deleteConceptAfterEtape(self, etape, sd):
406 Met a jour les etapes du JDC qui sont après etape suite a
407 la disparition du concept sd
409 # Cette methode est definie dans le noyau mais ne sert que pendant
410 # la phase de creation des etapes et des concepts. Il n'y a aucun
411 # traitement particulier a realiser.
412 # Dans d'autres conditions, il faut surcharger cette methode
416 N_OBJECT.OBJECT.supprime(self)
417 for etape in self.etapes:
420 def clean(self, netapes):
421 """Nettoie les `netapes` dernières etapes de la liste des etapes."""
424 for i in range(netapes):
425 e = self.etapes.pop()
431 del self.index_etapes[e]
433 def getFile(self, unite=None, fic_origine='', fname=None):
435 Retourne le nom du fichier correspondant a un numero d'unite
436 logique (entier) ainsi que le source contenu dans le fichier
439 # Si le JDC est relie a une application maitre, on delègue la
441 return self.appli.getFile(unite, fic_origine)
444 if os.path.exists("fort." + str(unite)):
445 fname = "fort." + str(unite)
447 raise AsException("Impossible de trouver le fichier correspondant"
448 " a l unite %s" % unite)
449 if not os.path.exists(fname):
450 raise AsException("%s n'est pas un fichier existant" % fname)
451 fproc = open(fname, 'r')
454 text = text.replace('\r\n', '\n')
455 linecache.cache[fname] = 0, 0, text.split('\n'), fname
458 def set_parLot(self, parLot, user_value=False):
460 Met le mode de traitement a PAR LOT
461 ou a COMMANDE par COMMANDE
462 en fonction de la valeur du mot cle PAR_LOT et
463 du contexte : application maitre ou pas
465 En PAR_LOT='NON', il n'y a pas d'ambiguite.
466 d'analyse et juste avant la phase d'execution.
467 `user_value` : permet de stocker la valeur choisie par l'utilisateur
468 pour l'interroger plus tard (par exemple dans `getContexteAvant`).
471 self.parLot_user = parLot
472 if self.appli == None:
473 # Pas d application maitre
476 # Avec application maitre
479 def accept(self, visitor):
481 Cette methode permet de parcourir l'arborescence des objets
482 en utilisant le pattern VISITEUR
484 visitor.visitJDC(self)
488 Cette methode a pour fonction d'ouvrir un interpreteur
489 pour que l'utilisateur entre des commandes interactivement
491 CONTEXT.setCurrentStep(self)
493 # Le module nommage utilise le module linecache pour acceder
494 # au source des commandes du jeu de commandes.
495 # Dans le cas d'un fichier, on accède au contenu de ce fichier
496 # Dans le cas de la console interactive, il faut pouvoir acceder
497 # aux commandes qui sont dans le buffer de la console
500 console = code.InteractiveConsole(
501 self.g_context, filename="<console>")
502 linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
503 banner = """***********************************************
504 * Interpreteur interactif %s
505 ***********************************************""" % self.code
506 console.interact(banner)
509 CONTEXT.unsetCurrentStep()
511 def getContexteAvant(self, etape):
513 Retourne le dictionnaire des concepts connus avant etape
514 On tient compte des commandes qui modifient le contexte
515 comme DETRUIRE ou les macros
516 Si etape == None, on retourne le contexte en fin de JDC
518 # L'etape courante pour laquelle le contexte a ete calcule est
519 # memorisee dans self.index_etape_courante
520 # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
522 # courante pendant le processus de construction des etapes.
523 # Si on insère des commandes (par ex, dans EFICAS), il faut prealablement
524 # remettre ce pointeur a 0
525 # self.current_context.items() if isinstance(v, ASSD)])
526 if self.parLot_user == 'NON':
527 d = self.current_context = self.g_context.copy()
530 # retirer les sd produites par 'etape'
531 sd_names = [sd.nom for sd in etape.getCreated_sd()]
536 from warnings import warn
538 "concept '%s' absent du contexte de %s" % (
540 RuntimeWarning, stacklevel=2)
543 index_etape = self.index_etapes[etape]
545 index_etape = len(self.etapes)
546 if index_etape >= self.index_etape_courante:
547 # On calcule le contexte en partant du contexte existant
548 d = self.current_context
549 if self.index_etape_courante == 0 and self.context_ini:
550 d.update(self.context_ini)
551 liste_etapes = self.etapes[self.index_etape_courante:index_etape]
553 d = self.current_context = {}
555 d.update(self.context_ini)
556 liste_etapes = self.etapes
558 for e in liste_etapes:
563 self.index_etape_courante = index_etape
566 def getGlobalContexte(self):
567 """Retourne "un" contexte global ;-)"""
568 # N'est utilise que par INCLUDE (sauf erreur).
569 # g_context est remis a {} en PAR_LOT='OUI'. const_context permet
570 # de retrouver ce qui y a ete mis par execCompile.
571 # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutes
572 # par getGlobalContexte de la MACRO.
573 d = self.const_context.copy()
574 d.update(self.g_context)
577 def getContexteCourant(self, etape_courante=None):
579 Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
580 de construction) au moment de l'execution de l'etape courante.
582 if etape_courante is None:
583 etape_courante = CONTEXT.getCurrentStep()
584 return self.getContexteAvant(etape_courante)
586 def getConcept(self, nomsd):
588 Methode pour recuperer un concept a partir de son nom
590 co = self.getContexteCourant().get(nomsd.strip(), None)
591 if not isinstance(co, ASSD):
595 def getConceptByType(self, nomsd, typesd, etape):
597 Methode pour recuperer un concept a partir de son nom et de son type.
598 Il aura comme père 'etape'.
600 assert issubclass(typesd, ASSD), typesd
601 co = typesd(etape=etape)
606 def del_concept(self, nomsd):
608 Methode pour supprimer la reference d'un concept dans le sds_dict.
609 Ne detruire pas le concept (different de supprime).
612 del self.sds_dict[nomsd.strip()]
616 def getCmd(self, nomcmd):
618 Methode pour recuperer la definition d'une commande
619 donnee par son nom dans les catalogues declares
622 for cata in (self.cata,):
623 if hasattr(cata, nomcmd):
624 return getattr(cata, nomcmd)
626 def append_reset(self, etape):
628 Ajoute une etape provenant d'un autre jdc a la liste des etapes
629 et remet a jour la parente de l'etape et des concepts
631 self.etapes.append(etape)
632 self.index_etapes[etape] = len(self.etapes) - 1
636 def sdAccessible(self):
637 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
640 print((' `- JDC sdAccessible : PAR_LOT =', self.parLot))
641 return self.parLot == 'NON'
643 def _build_reserved_kw_list(self):
644 """Construit la liste des mots-cles reserves (interdits pour le
645 nommage des concepts)."""
646 self._reserved_kw = set()
647 #for cat in self.cata:
649 self._reserved_kw.update(
650 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
651 self._reserved_kw.difference_update(
652 ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
653 'GEOM', 'MCSIMP', 'MCFACT'])