Salome HOME
debug intempestif
[tools/eficas.git] / Noyau / N_JDC.py
1 # coding=utf-8
2 # Copyright (C) 2007-2021   EDF R&D
3 #
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.
8 #
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.
13 #
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
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19
20
21 """
22    Ce module contient la classe JDC qui sert a interpreter un jeu de commandes
23 """
24
25 # Modules Python
26 from __future__ import absolute_import
27 from __future__ import print_function
28 try :
29     from builtins import str
30     from builtins import range
31 except : pass
32 import os
33 import traceback
34 import types
35 import sys
36 import linecache
37
38 # Modules EFICAS
39 from . import N_OBJECT
40 from . import N_CR
41 from .N_Exception import AsException, InterruptParsingError
42 from .N_ASSD import ASSD
43 from .strfunc import getEncoding
44
45
46 MemoryErrorMsg = """MemoryError :
47
48 En general, cette erreur se produit car la memoire utilisee hors du fortran
49 (jeveux) est importante.
50
51 Causes possibles :
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,
57    - ...
58
59 Solution :
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.
63 """
64
65
66 class JDC(N_OBJECT.OBJECT):
67
68     """
69        Cette classe interprete un jeu de commandes fourni sous
70        la forme d'une chaine de caractères
71
72        Attributs de classe :
73
74        Attributs d'instance :
75
76     """
77     nature = "JDC"
78     CR = N_CR.CR
79     exec_init = """
80 import Accas
81 from Accas import _F
82 from Accas import *
83 NONE = None
84 """
85
86     from .N_utils import SEP
87
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
93         self.cata = cata
94         self._build_reserved_kw_list()
95         self.cata_ordonne_dico = cata_ord_dico
96         self.nom = nom
97         self.appliEficas = appliEficas
98         self.parent = parent
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.
104         # Ceci reste a faire
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)
109         self.nstep = 0
110         self.nsd = 0
111         self.parLot = 'OUI'
112         self.parLot_user = None
113         if definition:
114             self.regles = definition.regles
115             self.code = definition.code
116         else:
117             self.regles = ()
118             self.code = "CODE"
119         #
120         #  Creation de l objet compte rendu pour collecte des erreurs
121         #
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
130         # le nom)
131         self.sdsDict = {}
132         self.etapes = []
133         self.index_etapes = {}
134         self.mc_globaux = {}
135         self.currentContext = {}
136         self.condition_context = {}
137         self.index_etape_courante = 0
138         self.UserError = "UserError"
139         self.alea = None
140         # permet transitoirement de conserver la liste des etapes
141         self.hist_etape = False
142
143     def compile(self):
144         """
145            Cette methode compile la chaine procedure
146            Si des erreurs se produisent, elles sont consignees dans le
147            compte-rendu self.cr
148         """
149         try:
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
152             # traceback
153             encoded_filename = self.nom.encode(getEncoding())
154             self.proc_compile = compile(
155                 self.procedure, encoded_filename, 'exec')
156         except SyntaxError as e:
157             if CONTEXT.debug:
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 = """
165 Causes possibles :
166  - offset too large : liste trop longue derrière un mot-cle.
167    Solution : liste = (valeurs, ..., )
168               MOT_CLE = *liste,
169 """
170             l = traceback.format_exception_only(SystemError, e)
171             l.append(erreurs_connues)
172             self.cr.exception("Compilation impossible : " + ''.join(l))
173         return
174
175     def setCurrentContext(self):
176         # beaucoup trop simple Ne tient pas compte des imports
177         # et des include
178         # ne sert que pour le POC
179         CONTEXT.setCurrentStep(self)
180
181     def execCompile(self):
182         """
183            Cette methode execute le jeu de commandes compile dans le contexte
184            self.g_context de l'objet JDC
185         """
186
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
193         import linecache
194         linecache.cache[self.nom] = 0, 0, self.procedure.split('\n'), self.nom
195         try:
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)
201                 else :
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() :
205                         self.g_context[k]=v
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()
209
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)
213             if self.context_ini:
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
219
220             #if self.appliEficas != None:
221             #    self.appliEficas.afficheInfos(
222             #        'Interpretation du fichier de commandes en cours ...')
223
224             # On sauve le contexte pour garder la memoire des constantes
225             # En mode edition (EFICAS) ou lors des verifications le contexte
226             # est recalcule
227             # mais les constantes sont perdues
228             self.const_context = self.g_context
229             exec(self.proc_compile, self.g_context)
230
231             CONTEXT.unsetCurrentStep()
232
233         except InterruptParsingError:
234             # interrupt the command file parsing used by FIN to ignore the end
235             # of the file
236             pass
237
238         except EOFError:
239             # Exception utilise pour interrompre un jeu
240             # de commandes avant la fin
241             # Fonctionnement normal, ne doit pas etre considere comme une
242             # erreur
243             CONTEXT.unsetCurrentStep()
244             self.afficheFinExec()
245             self.traiterFinExec('commande')
246
247         except AsException as e:
248             # une erreur a ete identifiee
249             if CONTEXT.debug:
250                 traceback.print_exc()
251             # l'exception a ete recuperee avant (ou, comment ?),
252             # donc on cherche dans le texte
253             txt = str(e)
254             if txt.find('MemoryError') >= 0:
255                 txt = MemoryErrorMsg
256             self.cr.exception(txt)
257             CONTEXT.unsetCurrentStep()
258
259         except NameError as e:
260             etype, value, tb = sys.exc_info()
261             l = traceback.extract_tb(tb)
262             s = traceback.format_exception_only("Erreur de nom", e)[0][:-1]
263             msg = "erreur de syntaxe,  %s ligne %d" % (s, l[-1][1])
264             if CONTEXT.debug:
265                 traceback.print_exc()
266             self.cr.exception(msg)
267             CONTEXT.unsetCurrentStep()
268
269        # except self.UserError as exc_val:
270        #     self.traiterUserException(exc_val)
271        #     CONTEXT.unsetCurrentStep()
272        #     self.afficheFinExec()
273        #     self.traiterFinExec('commande')
274
275         except:
276             # erreur inattendue
277             # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
278             # (tuple de 3 elements)
279             if CONTEXT.debug:
280                 traceback.print_exc()
281
282             traceback.print_exc()
283
284             exc_typ, exc_val, exc_fr = sys.exc_info()
285             l = traceback.format_exception(exc_typ, exc_val, exc_fr)
286             self.cr.exception(
287                 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + ''.join(l))
288             del exc_typ, exc_val, exc_fr
289             CONTEXT.unsetCurrentStep()
290         idx=0
291         for e in self.etapes:
292             self.enregistreEtapePyxb(e,idx)
293             idx=idx+1
294
295     def afficheFinExec(self):
296         """
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
301         """
302         return
303
304     def traiterFinExec(self, mode, etape=None):
305         """
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
310         """
311         print ( "FIN D'EXECUTION %s %s" %s( mode, etape))
312
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.
317         """
318         return
319
320     def register(self, etape):
321         """
322            Cette methode ajoute etape dans la liste des etapes : self.etapes
323            et retourne un numero d'enregistrement
324         """
325         self.etapes.append(etape)
326         self.index_etapes[etape] = len(self.etapes) - 1
327         return self.gRegister(etape)
328
329     def o_register(self, sd):
330         """
331            Retourne un identificateur pour concept
332         """
333         self.nsd = self.nsd + 1
334         nom = sd.idracine + self.SEP + repr(self.nsd)
335         return nom
336
337     def gRegister(self, etape):
338         """
339             Retourne un identificateur pour etape
340         """
341         self.nstep = self.nstep + 1
342         idetape = etape.idracine + self.SEP + repr(self.nstep)
343         return idetape
344
345     def createSdprod(self, etape, nomsd):
346         """
347             Cette methode doit fabriquer le concept produit retourne
348             par l'etape etape et le nommer.
349
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)
353
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.
358
359             Deux cas possibles :
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
363                             etape.
364             Dans le cas du JDC, le deuxième cas ne peut pas se produire.
365         """
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)
371         return sd
372
373     def nommerSDProd(self, sd, sdnom, restrict='non'):
374         """
375             Nomme la SD apres avoir verifie que le nommage est possible : nom
376             non utilise
377             Si le nom est deja utilise, leve une exception
378             Met le concept cree dans le concept global g_context
379         """
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:
384             raise AsException(
385                 "Nom de concept invalide. '%s' est un mot-cle reserve." % sdnom)
386
387         # Ajoute a la creation (appel de regSD).
388         self.sdsDict[sdnom] = sd
389         sd.setName(sdnom)
390
391         # En plus si restrict vaut 'non', on insere le concept dans le contexte
392         # du JDC
393         if restrict == 'non':
394             self.g_context[sdnom] = sd
395
396     def regUserSD(self,sd):
397     # utilisee pour creer les references
398     # se contente d appeler la methode equivalente sur le jdc
399         id=self.regSD(sd)
400         self.nommerSDProd(sd,sd.nom)
401         return id
402
403
404     def regSD(self, sd):
405         """
406             Methode appelee dans l __init__ d un ASSD lors de sa creation
407             pour s enregistrer
408         """
409         return self.o_register(sd)
410
411     def deleteConceptAfterEtape(self, etape, sd):
412         """
413             Met a jour les etapes du JDC qui sont après etape suite a
414             la disparition du concept sd
415         """
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
420         return
421
422     def supprime(self):
423         N_OBJECT.OBJECT.supprime(self)
424         for etape in self.etapes:
425             etape.supprime()
426
427     def clean(self, netapes):
428         """Nettoie les `netapes` dernières etapes de la liste des etapes."""
429         if self.hist_etape:
430             return
431         for i in range(netapes):
432             e = self.etapes.pop()
433             jdc = e.jdc
434             parent = e.parent
435             e.supprime()
436             e.parent = parent
437             e.jdc = jdc
438             del self.index_etapes[e]
439
440     def getFile(self, unite=None, fic_origine='', fname=None):
441         """
442             Retourne le nom du fichier correspondant a un numero d'unite
443             logique (entier) ainsi que le source contenu dans le fichier
444         """
445         #if self.appliEficas:
446             # Si le JDC est relie a une appliEficascation maitre, on delègue la
447             # recherche
448         #    return self.appliEficas.getFile(unite, fic_origine)
449         #else:
450         #    if unite != None:
451         #        if os.path.exists("fort." + str(unite)):
452         #            fname = "fort." + str(unite)
453         if fname == None:
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')
458         text = fproc.read()
459         fproc.close()
460         text = text.replace('\r\n', '\n')
461         linecache.cache[fname] = 0, 0, text.split('\n'), fname
462         return fname, text
463
464     def set_parLot(self, parLot, user_value=False):
465         """
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
470
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`).
475         """
476         if user_value:
477             self.parLot_user = parLot
478         if self.appliEficas == None:
479             # Pas d appliEficascation maitre
480             self.parLot = parLot
481         else:
482             # Avec appliEficascation maitre
483             self.parLot = 'OUI'
484
485     def accept(self, visitor):
486         """
487            Cette methode permet de parcourir l'arborescence des objets
488            en utilisant le pattern VISITEUR
489         """
490         visitor.visitJDC(self)
491
492     def interact(self):
493         """
494             Cette methode a pour fonction d'ouvrir un interpreteur
495             pour que l'utilisateur entre des commandes interactivement
496         """
497         CONTEXT.setCurrentStep(self)
498         try:
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
504             import linecache
505             import code
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)
513         finally:
514             console = None
515             CONTEXT.unsetCurrentStep()
516
517     def getContexteAvant(self, etape):
518         """
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
523         """
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
527         # memorisant l'etape
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()
534         #    if etape is None:
535         #        return d
536             # retirer les sd produites par 'etape'
537         #    sd_names = [sd.nom for sd in etape.getCreated_sd()]
538         #    for nom in sd_names:
539         #        try:
540         #            del d[nom]
541         #        except KeyError:
542         #            from warnings import warn
543         #            warn(
544         #                "concept '%s' absent du contexte de %s" % (
545         #                    nom, self.nom),
546         #                RuntimeWarning, stacklevel=2)
547         #    return d
548         if etape:
549             index_etape = self.index_etapes[etape]
550         else:
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]
558         else:
559             d = self.currentContext = {}
560             if self.context_ini:
561                 d.update(self.context_ini)
562             liste_etapes = self.etapes
563
564         for e in liste_etapes:
565             if e is etape:
566                 break
567             if e.isActif():
568                 e.updateContext(d)
569         self.index_etape_courante = index_etape
570         return d
571
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)
581         return d
582
583     def getContexteCourant(self, etape_courante=None):
584         """
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.
587         """
588         if etape_courante is None:
589             etape_courante = CONTEXT.getCurrentStep()
590         return self.getContexteAvant(etape_courante)
591
592     def getConcept(self, nomsd):
593         """
594             Methode pour recuperer un concept a partir de son nom
595         """
596         co = self.getContexteCourant().get(nomsd.strip(), None)
597         if not isinstance(co, ASSD):
598             co = None
599         return co
600
601     def getConceptByType(self, nomsd, typesd, etape):
602         """
603             Methode pour recuperer un concept a partir de son nom et de son type.
604             Il aura comme père 'etape'.
605         """
606         assert issubclass(typesd, ASSD), typesd
607         co = typesd(etape=etape)
608         co.setName(nomsd)
609         co.executed = 1
610         return co
611
612     def delConcept(self, nomsd):
613         """
614            Methode pour supprimer la reference d'un concept dans le sdsDict.
615            Ne detruire pas le concept (different de supprime).
616         """
617         try:
618             del self.sdsDict[nomsd.strip()]
619         except:
620             pass
621
622     def getCmd(self, nomcmd):
623         """
624             Methode pour recuperer la definition d'une commande
625             donnee par son nom dans les catalogues declares
626             au niveau du jdc
627         """
628         for cata in (self.cata,):
629             if hasattr(cata, nomcmd):
630                 return getattr(cata, nomcmd)
631
632     def append_reset(self, etape):
633         """
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
636         """
637         self.etapes.append(etape)
638         self.index_etapes[etape] = len(self.etapes) - 1
639         etape.reparent(self)
640         etape.resetJdc(self)
641
642     def sdAccessible(self):
643         """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
644         """
645         if CONTEXT.debug:
646             print((' `- JDC sdAccessible : PAR_LOT =', self.parLot))
647         return self.parLot == 'NON'
648
649     def getEtapesByName(self,name):
650         listeDEtapes=[]
651         for e in self.etapes :
652             if e.nom  == name : listeDEtapes.append(e) 
653         return listeDEtapes
654
655     def getEtapeByConceptName(self,conceptName):
656         for e in self.etapes :
657             if e.sdnom  == conceptName :  return e 
658
659
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:
665         cat=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'])
672