2 # Copyright (C) 2007-2021 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
46 MemoryErrorMsg = """MemoryError :
48 En general, cette erreur se produit car la memoire utilisee 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-meme,
54 - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
55 qui a besoin de memoire hors jeveux,
56 - utilisation de jeveux dynamique,
60 - distinguer la memoire limite du calcul (case "Memoire totale" de astk)
61 de la memoire reservee a jeveux (case "dont Aster"), le reste etant
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', appliEficas=None, context_ini=None, **args):
91 self.procedure = procedure
92 self.definition = definition
94 self._build_reserved_kw_list()
95 self.cata_ordonne_dico = cata_ord_dico
97 self.appliEficas = appliEficas
99 self.context_ini = context_ini
100 # On conserve les arguments supplementaires. Il est possible de passer
101 # des informations globales au JDC par ce moyen. Il pourrait etre plus
102 # sur de mettre en place le mecanisme des mots-cles pour verifier la
103 # validite des valeurs passees.
105 # On initialise avec les parametres de la definition puis on
106 # update avec ceux du JDC
107 self.args = self.definition.args
108 self.args.update(args)
112 self.parLot_user = None
114 self.regles = definition.regles
115 self.code = definition.code
120 # Creation de l objet compte rendu pour collecte des erreurs
122 self.cr = self.CR(debut="CR phase d'initialisation",
123 fin="fin CR phase d'initialisation")
124 # on met le jdc lui-meme dans le context global pour l'avoir sous
125 # l'etiquette "jdc" dans le fichier de commandes
126 self.g_context = {'jdc': self}
127 CONTEXT.unsetCurrentJdC()
128 CONTEXT.setCurrentJdC(self)
129 # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par
133 self.index_etapes = {}
135 self.currentContext = {}
136 self.condition_context = {}
137 self.index_etape_courante = 0
138 self.UserError = "UserError"
140 # permet transitoirement de conserver la liste des etapes
141 self.hist_etape = False
145 Cette methode compile la chaine procedure
146 Si des erreurs se produisent, elles sont consignees dans le
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(getEncoding())
154 self.proc_compile = compile(
155 self.procedure, encoded_filename, 'exec')
156 except SyntaxError as e:
158 traceback.print_exc()
159 l = traceback.format_exception_only(SyntaxError, e)
160 self.cr.exception("Compilation impossible : " + ''.join(l))
161 except MemoryError as e:
162 self.cr.exception(MemoryErrorMsg)
163 except SystemError as e:
164 erreurs_connues = """
166 - offset too large : liste trop longue derrière un mot-cle.
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 setCurrentContext(self):
176 # beaucoup trop simple Ne tient pas compte des imports
178 # ne sert que pour le POC
179 CONTEXT.setCurrentStep(self)
181 def execCompile(self):
183 Cette methode execute le jeu de commandes compile dans le contexte
184 self.g_context de l'objet JDC
187 CONTEXT.setCurrentStep(self)
188 # Le module nommage utilise le module linecache pour acceder
189 # au source des commandes du jeu de commandes.
190 # Dans le cas d'un fichier, on accède au contenu de ce fichier
191 # Dans le cas d'une chaine de caractères il faut acceder
192 # aux commandes qui sont dans la chaine
194 linecache.cache[self.nom] = 0, 0, self.procedure.split('\n'), self.nom
196 exec(self.exec_init, self.g_context)
197 for obj_cata in (self.cata,):
198 if type(obj_cata) == types.ModuleType:
199 init2 = "from " + obj_cata.__name__ + " import *"
200 exec(init2, self.g_context)
202 # ici on a un catalogue en grammaire Eficas XML
203 # il faut ajouter ce qu on a construit au contexte
204 for (k,v) in obj_cata.contexteXML.items() :
206 # Initialisation du contexte global pour l'evaluation des conditions de BLOC
207 # On utilise une copie de l'initialisation du contexte du jdc
208 self.condition_context = self.g_context.copy()
210 # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
211 # le contexte initial (--> permet d'evaluer un JDC en recuperant un contexte
212 # d'un autre par exemple)
214 self.g_context.update(self.context_ini)
215 # Update du dictionnaire des concepts
216 for sdnom, sd in list(self.context_ini.items()):
217 if isinstance(sd, ASSD):
218 self.sdsDict[sdnom] = sd
220 #if self.appliEficas != None:
221 # self.appliEficas.afficheInfos(
222 # 'Interpretation du fichier de commandes en cours ...')
224 # On sauve le contexte pour garder la memoire des constantes
225 # En mode edition (EFICAS) ou lors des verifications le contexte
227 # mais les constantes sont perdues
228 self.const_context = self.g_context
229 exec(self.proc_compile, self.g_context)
231 CONTEXT.unsetCurrentStep()
233 except InterruptParsingError:
234 # interrupt the command file parsing used by FIN to ignore the end
239 # Exception utilise pour interrompre un jeu
240 # de commandes avant la fin
241 # Fonctionnement normal, ne doit pas etre considere comme une
243 CONTEXT.unsetCurrentStep()
244 self.afficheFinExec()
245 self.traiterFinExec('commande')
247 except AsException as e:
248 # une erreur a ete identifiee
250 traceback.print_exc()
251 # l'exception a ete recuperee avant (ou, comment ?),
252 # donc on cherche dans le texte
254 if txt.find('MemoryError') >= 0:
256 self.cr.exception(txt)
257 CONTEXT.unsetCurrentStep()
259 except NameError as e:
260 etype, value, tb = sys.exc_info()
261 l = traceback.extract_tb(tb)
262 s = traceback.format_exception_only(NameError,e)
263 msg = "erreur de syntaxe, %s ligne %d" % (s, l[-1][1])
265 traceback.print_exc()
266 self.cr.exception(msg)
267 CONTEXT.unsetCurrentStep()
269 # except self.UserError as exc_val:
270 # self.traiterUserException(exc_val)
271 # CONTEXT.unsetCurrentStep()
272 # self.afficheFinExec()
273 # self.traiterFinExec('commande')
277 # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
278 # (tuple de 3 elements)
280 traceback.print_exc()
282 traceback.print_exc()
284 exc_typ, exc_val, exc_fr = sys.exc_info()
285 l = traceback.format_exception(exc_typ, exc_val, exc_fr)
287 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + ''.join(l))
288 del exc_typ, exc_val, exc_fr
289 CONTEXT.unsetCurrentStep()
291 for e in self.etapes:
292 self.enregistreEtapePyxb(e,idx)
295 def afficheFinExec(self):
297 Cette methode realise l'affichage final des statistiques de temps
298 apres l'execution de toutes
299 les commandes en mode commande par commande ou par lot
300 Elle doit etre surchargee pour en introduire un
304 def traiterFinExec(self, mode, etape=None):
306 Cette methode realise un traitement final apres l'execution de toutes
307 les commandes en mode commande par commande ou par lot
308 Par defaut il n'y a pas de traitement. Elle doit etre surchargee
309 pour en introduire un
311 print ( "FIN D'EXECUTION %s %s" %s( mode, etape))
313 def traiterUserException(self, exc_val):
314 """Cette methode realise un traitement sur les exceptions utilisateur
315 Par defaut il n'y a pas de traitement. La methode doit etre
316 surchargee pour en introduire un.
320 def register(self, etape):
322 Cette methode ajoute etape dans la liste des etapes : self.etapes
323 et retourne un numero d'enregistrement
325 self.etapes.append(etape)
326 self.index_etapes[etape] = len(self.etapes) - 1
327 return self.gRegister(etape)
329 def o_register(self, sd):
331 Retourne un identificateur pour concept
333 self.nsd = self.nsd + 1
334 nom = sd.idracine + self.SEP + repr(self.nsd)
337 def gRegister(self, etape):
339 Retourne un identificateur pour etape
341 self.nstep = self.nstep + 1
342 idetape = etape.idracine + self.SEP + repr(self.nstep)
345 def createSdprod(self, etape, nomsd):
347 Cette methode doit fabriquer le concept produit retourne
348 par l'etape etape et le nommer.
350 Elle est appelee a l'initiative de l'etape
351 pendant le processus de construction de cette etape :
352 methode __call__ de la classe CMD (OPER ou MACRO)
354 Ce travail est realise par le contexte superieur
355 (etape.parent) car dans certains cas, le concept ne doit
356 pas etre fabrique mais l'etape doit simplement utiliser
357 un concept preexistant.
360 - Cas 1 : etape.reuse != None : le concept est reutilise
361 - Cas 2 : l'etape appartient a une macro qui a declare un
362 concept de sortie qui doit etre produit par cette
364 Dans le cas du JDC, le deuxième cas ne peut pas se produire.
366 sd = etape.getSdProd()
367 if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
368 # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
369 # d un concept. Commande non reentrante ou reuse absent.
370 self.nommerSDProd(sd, nomsd)
373 def nommerSDProd(self, sd, sdnom, restrict='non'):
375 Nomme la SD apres avoir verifie que le nommage est possible : nom
377 Si le nom est deja utilise, leve une exception
378 Met le concept cree dans le concept global g_context
380 o = self.sdsDict.get(sdnom, None)
381 if isinstance(o, ASSD):
382 raise AsException("Nom de concept deja defini : %s" % sdnom)
383 if sdnom in self._reserved_kw:
385 "Nom de concept invalide. '%s' est un mot-cle reserve." % sdnom)
387 # Ajoute a la creation (appel de regSD).
388 self.sdsDict[sdnom] = sd
391 # En plus si restrict vaut 'non', on insere le concept dans le contexte
393 if restrict == 'non':
394 self.g_context[sdnom] = sd
396 def regUserSD(self,sd):
397 # utilisee pour creer les references
398 # se contente d appeler la methode equivalente sur le jdc
400 self.nommerSDProd(sd,sd.nom)
406 Methode appelee dans l __init__ d un ASSD lors de sa creation
409 return self.o_register(sd)
411 def deleteConceptAfterEtape(self, etape, sd):
413 Met a jour les etapes du JDC qui sont après etape suite a
414 la disparition du concept sd
416 # Cette methode est definie dans le noyau mais ne sert que pendant
417 # la phase de creation des etapes et des concepts. Il n'y a aucun
418 # traitement particulier a realiser.
419 # Dans d'autres conditions, il faut surcharger cette methode
423 N_OBJECT.OBJECT.supprime(self)
424 for etape in self.etapes:
427 def clean(self, netapes):
428 """Nettoie les `netapes` dernières etapes de la liste des etapes."""
431 for i in range(netapes):
432 e = self.etapes.pop()
438 del self.index_etapes[e]
440 def getFile(self, unite=None, fic_origine='', fname=None):
442 Retourne le nom du fichier correspondant a un numero d'unite
443 logique (entier) ainsi que le source contenu dans le fichier
445 #if self.appliEficas:
446 # Si le JDC est relie a une appliEficascation maitre, on delègue la
448 # return self.appliEficas.getFile(unite, fic_origine)
451 # if os.path.exists("fort." + str(unite)):
452 # fname = "fort." + str(unite)
454 raise AsException("Impossible de trouver le fichier correspondant")
455 if not os.path.exists(fname):
456 raise AsException(fname + " n'est pas un fichier existant" )
457 fproc = open(fname, 'r')
460 text = text.replace('\r\n', '\n')
461 linecache.cache[fname] = 0, 0, text.split('\n'), fname
464 def set_parLot(self, parLot, user_value=False):
466 Met le mode de traitement a PAR LOT
467 ou a COMMANDE par COMMANDE
468 en fonction de la valeur du mot cle PAR_LOT et
469 du contexte : appliEficascation maitre ou pas
471 En PAR_LOT='NON', il n'y a pas d'ambiguite.
472 d'analyse et juste avant la phase d'execution.
473 `user_value` : permet de stocker la valeur choisie par l'utilisateur
474 pour l'interroger plus tard (par exemple dans `getContexteAvant`).
477 self.parLot_user = parLot
478 if self.appliEficas == None:
479 # Pas d appliEficascation maitre
482 # Avec appliEficascation maitre
485 def accept(self, visitor):
487 Cette methode permet de parcourir l'arborescence des objets
488 en utilisant le pattern VISITEUR
490 visitor.visitJDC(self)
494 Cette methode a pour fonction d'ouvrir un interpreteur
495 pour que l'utilisateur entre des commandes interactivement
497 CONTEXT.setCurrentStep(self)
499 # Le module nommage utilise le module linecache pour acceder
500 # au source des commandes du jeu de commandes.
501 # Dans le cas d'un fichier, on accède au contenu de ce fichier
502 # Dans le cas de la console interactive, il faut pouvoir acceder
503 # aux commandes qui sont dans le buffer de la console
506 console = code.InteractiveConsole(
507 self.g_context, filename="<console>")
508 linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
509 banner = """***********************************************
510 * Interpreteur interactif %s
511 ***********************************************""" % self.code
512 console.interact(banner)
515 CONTEXT.unsetCurrentStep()
517 def getContexteAvant(self, etape):
519 Retourne le dictionnaire des concepts connus avant etape
520 On tient compte des commandes qui modifient le contexte
521 comme DETRUIRE ou les macros
522 Si etape == None, on retourne le contexte en fin de JDC
524 # L'etape courante pour laquelle le contexte a ete calcule est
525 # memorisee dans self.index_etape_courante
526 # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
528 # courante pendant le processus de construction des etapes.
529 # Si on insère des commandes (par ex, dans EFICAS), il faut prealablement
530 # remettre ce pointeur a 0
531 # self.currentContext.items() if isinstance(v, ASSD)])
532 #if self.parLot_user == 'NON':
533 # d = self.currentContext = self.g_context.copy()
536 # retirer les sd produites par 'etape'
537 # sd_names = [sd.nom for sd in etape.getCreated_sd()]
538 # for nom in sd_names:
542 # from warnings import warn
544 # "concept '%s' absent du contexte de %s" % (
546 # RuntimeWarning, stacklevel=2)
549 index_etape = self.index_etapes[etape]
551 index_etape = len(self.etapes)
552 if index_etape >= self.index_etape_courante:
553 # On calcule le contexte en partant du contexte existant
554 d = self.currentContext
555 if self.index_etape_courante == 0 and self.context_ini:
556 d.update(self.context_ini)
557 liste_etapes = self.etapes[self.index_etape_courante:index_etape]
559 d = self.currentContext = {}
561 d.update(self.context_ini)
562 liste_etapes = self.etapes
564 for e in liste_etapes:
569 self.index_etape_courante = index_etape
572 def getGlobalContexte(self):
573 """Retourne "un" contexte global ;-)"""
574 # N'est utilise que par INCLUDE (sauf erreur).
575 # g_context est remis a {} en PAR_LOT='OUI'. const_context permet
576 # de retrouver ce qui y a ete mis par execCompile.
577 # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutes
578 # par getGlobalContexte de la MACRO.
579 d = self.const_context.copy()
580 d.update(self.g_context)
583 def getContexteCourant(self, etape_courante=None):
585 Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
586 de construction) au moment de l'execution de l'etape courante.
588 if etape_courante is None:
589 etape_courante = CONTEXT.getCurrentStep()
590 return self.getContexteAvant(etape_courante)
592 def getConcept(self, nomsd):
594 Methode pour recuperer un concept a partir de son nom
596 co = self.getContexteCourant().get(nomsd.strip(), None)
597 if not isinstance(co, ASSD):
601 def getConceptByType(self, nomsd, typesd, etape):
603 Methode pour recuperer un concept a partir de son nom et de son type.
604 Il aura comme père 'etape'.
606 assert issubclass(typesd, ASSD), typesd
607 co = typesd(etape=etape)
612 def delConcept(self, nomsd):
614 Methode pour supprimer la reference d'un concept dans le sdsDict.
615 Ne detruire pas le concept (different de supprime).
618 del self.sdsDict[nomsd.strip()]
622 def getCmd(self, nomcmd):
624 Methode pour recuperer la definition d'une commande
625 donnee par son nom dans les catalogues declares
628 for cata in (self.cata,):
629 if hasattr(cata, nomcmd):
630 return getattr(cata, nomcmd)
632 def append_reset(self, etape):
634 Ajoute une etape provenant d'un autre jdc a la liste des etapes
635 et remet a jour la parente de l'etape et des concepts
637 self.etapes.append(etape)
638 self.index_etapes[etape] = len(self.etapes) - 1
642 def sdAccessible(self):
643 """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
646 print((' `- JDC sdAccessible : PAR_LOT =', self.parLot))
647 return self.parLot == 'NON'
649 def getEtapesByName(self,name):
651 for e in self.etapes :
652 if e.nom == name : listeDEtapes.append(e)
655 def getEtapeByConceptName(self,conceptName):
656 for e in self.etapes :
657 if hasattr(e,'sdnom') and e.sdnom == conceptName : return e
660 def _build_reserved_kw_list(self):
661 """Construit la liste des mots-cles reserves (interdits pour le
662 nommage des concepts)."""
663 self._reserved_kw = set()
664 #for cat in self.cata:
666 self._reserved_kw.update(
667 #PN 14 2020 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
668 [kw for kw in dir(cat) ])
669 self._reserved_kw.difference_update(
670 ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
671 'GEOM', 'MCSIMP', 'MCFACT'])