Salome HOME
modif pour MT
[tools/eficas.git] / Noyau / N_JDC.py
1 # coding=utf-8
2 # Copyright (C) 2007-2017   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 from six.moves import range
45
46
47 MemoryErrorMsg = """MemoryError :
48
49 En general, cette erreur se produit car la memoire utilisee hors du fortran
50 (jeveux) est importante.
51
52 Causes possibles :
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,
58    - ...
59
60 Solution :
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.
64 """
65
66
67 class JDC(N_OBJECT.OBJECT):
68
69     """
70        Cette classe interprete un jeu de commandes fourni sous
71        la forme d'une chaine de caractères
72
73        Attributs de classe :
74
75        Attributs d'instance :
76
77     """
78     nature = "JDC"
79     CR = N_CR.CR
80     exec_init = """
81 import Accas
82 from Accas import _F
83 from Accas import *
84 NONE = None
85 """
86
87     from .N_utils import SEP
88
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
94         self.cata = cata
95         # PN pourquoi ?
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
100         self.nom = nom
101         self.appli = appli
102         self.parent = parent
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.
108         # Ceci reste a faire
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)
113         self.nstep = 0
114         self.nsd = 0
115         self.parLot = 'OUI'
116         self.parLot_user = None
117         if definition:
118             self.regles = definition.regles
119             self.code = definition.code
120         else:
121             self.regles = ()
122             self.code = "CODE"
123         #
124         #  Creation de l objet compte rendu pour collecte des erreurs
125         #
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
132         # le nom)
133         self.sds_dict = {}
134         self.etapes = []
135         self.index_etapes = {}
136         self.mc_globaux = {}
137         self.current_context = {}
138         self.condition_context = {}
139         self.index_etape_courante = 0
140         self.UserError = "UserError"
141         self.alea = None
142         # permet transitoirement de conserver la liste des etapes
143         self.hist_etape = False
144
145     def compile(self):
146         """
147            Cette methode compile la chaine procedure
148            Si des erreurs se produisent, elles sont consignees dans le
149            compte-rendu self.cr
150         """
151         try:
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
157             # traceback
158             encoded_filename = self.nom.encode(getEncoding())
159             self.proc_compile = compile(
160                 self.procedure, encoded_filename, 'exec')
161         except SyntaxError as e:
162             if CONTEXT.debug:
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 = """
170 Causes possibles :
171  - offset too large : liste trop longue derrière un mot-cle.
172    Solution : liste = (valeurs, ..., )
173               MOT_CLE = *liste,
174 """
175             l = traceback.format_exception_only(SystemError, e)
176             l.append(erreurs_connues)
177             self.cr.exception("Compilation impossible : " + ''.join(l))
178         return
179
180     def setCurrentContext(self):
181         # beaucoup trop simple Ne tient pas compte des imports
182         # et des include
183         # ne sert que pour le POC
184         CONTEXT.setCurrentStep(self)
185
186     def execCompile(self):
187         """
188            Cette methode execute le jeu de commandes compile dans le contexte
189            self.g_context de l'objet JDC
190         """
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
197         import linecache
198         linecache.cache[self.nom] = 0, 0, self.procedure.split('\n'), self.nom
199         try:
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)
205                 else :
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() :
209                        self.g_context[k]=v
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()
213
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)
217             if self.context_ini:
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
223
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
229             # est recalcule
230             # mais les constantes sont perdues
231             self.const_context = self.g_context
232             exec(self.proc_compile, self.g_context)
233
234             CONTEXT.unsetCurrentStep()
235             if self.appli != None:
236                 self.appli.afficheInfos('')
237
238         except InterruptParsingError:
239             # interrupt the command file parsing used by FIN to ignore the end
240             # of the file
241             pass
242
243         except EOFError:
244             # Exception utilise pour interrompre un jeu
245             # de commandes avant la fin
246             # Fonctionnement normal, ne doit pas etre considere comme une
247             # erreur
248             CONTEXT.unsetCurrentStep()
249             self.afficheFinExec()
250             self.traiterFinExec('commande')
251
252         except AsException as e:
253             # une erreur a ete identifiee
254             if CONTEXT.debug:
255                 traceback.print_exc()
256             # l'exception a ete recuperee avant (ou, comment ?),
257             # donc on cherche dans le texte
258             txt = str(e)
259             if txt.find('MemoryError') >= 0:
260                 txt = MemoryErrorMsg
261             self.cr.exception(txt)
262             CONTEXT.unsetCurrentStep()
263
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])
269             if CONTEXT.debug:
270                 traceback.print_exc()
271             self.cr.exception(msg)
272             CONTEXT.unsetCurrentStep()
273
274        # except self.UserError as exc_val:
275        #     self.traiterUserException(exc_val)
276        #     CONTEXT.unsetCurrentStep()
277        #     self.afficheFinExec()
278        #     self.traiterFinExec('commande')
279
280         except:
281             # erreur inattendue
282             # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
283             # (tuple de 3 elements)
284             if CONTEXT.debug:
285                 traceback.print_exc()
286
287             traceback.print_exc()
288
289             exc_typ, exc_val, exc_fr = sys.exc_info()
290             l = traceback.format_exception(exc_typ, exc_val, exc_fr)
291             self.cr.exception(
292                 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + ''.join(l))
293             del exc_typ, exc_val, exc_fr
294             CONTEXT.unsetCurrentStep()
295
296     def afficheFinExec(self):
297         """
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
302         """
303         return
304
305     def traiterFinExec(self, mode, etape=None):
306         """
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
311         """
312         print ( "FIN D'EXECUTION %s %s" %s( mode, etape))
313
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.
318         """
319         return
320
321     def register(self, etape):
322         """
323            Cette methode ajoute etape dans la liste des etapes : self.etapes
324            et retourne un numero d'enregistrement
325         """
326         self.etapes.append(etape)
327         self.index_etapes[etape] = len(self.etapes) - 1
328         return self.gRegister(etape)
329
330     def o_register(self, sd):
331         """
332            Retourne un identificateur pour concept
333         """
334         self.nsd = self.nsd + 1
335         nom = sd.idracine + self.SEP + repr(self.nsd)
336         return nom
337
338     def gRegister(self, etape):
339         """
340             Retourne un identificateur pour etape
341         """
342         self.nstep = self.nstep + 1
343         idetape = etape.idracine + self.SEP + repr(self.nstep)
344         return idetape
345
346     def createSdprod(self, etape, nomsd):
347         """
348             Cette methode doit fabriquer le concept produit retourne
349             par l'etape etape et le nommer.
350
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)
354
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.
359
360             Deux cas possibles :
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
364                             etape.
365             Dans le cas du JDC, le deuxième cas ne peut pas se produire.
366         """
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)
372         return sd
373
374     def NommerSdprod(self, sd, sdnom, restrict='non'):
375         """
376             Nomme la SD apres avoir verifie que le nommage est possible : nom
377             non utilise
378             Si le nom est deja utilise, leve une exception
379             Met le concept cree dans le concept global g_context
380         """
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:
385             raise AsException(
386                 "Nom de concept invalide. '%s' est un mot-cle reserve." % sdnom)
387
388         # Ajoute a la creation (appel de regSD).
389         self.sds_dict[sdnom] = sd
390         sd.setName(sdnom)
391
392         # En plus si restrict vaut 'non', on insere le concept dans le contexte
393         # du JDC
394         if restrict == 'non':
395             self.g_context[sdnom] = sd
396
397     def regSD(self, sd):
398         """
399             Methode appelee dans l __init__ d un ASSD lors de sa creation
400             pour s enregistrer
401         """
402         return self.o_register(sd)
403
404     def deleteConceptAfterEtape(self, etape, sd):
405         """
406             Met a jour les etapes du JDC qui sont après etape suite a
407             la disparition du concept sd
408         """
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
413         return
414
415     def supprime(self):
416         N_OBJECT.OBJECT.supprime(self)
417         for etape in self.etapes:
418             etape.supprime()
419
420     def clean(self, netapes):
421         """Nettoie les `netapes` dernières etapes de la liste des etapes."""
422         if self.hist_etape:
423             return
424         for i in range(netapes):
425             e = self.etapes.pop()
426             jdc = e.jdc
427             parent = e.parent
428             e.supprime()
429             e.parent = parent
430             e.jdc = jdc
431             del self.index_etapes[e]
432
433     def getFile(self, unite=None, fic_origine='', fname=None):
434         """
435             Retourne le nom du fichier correspondant a un numero d'unite
436             logique (entier) ainsi que le source contenu dans le fichier
437         """
438         if self.appli:
439             # Si le JDC est relie a une application maitre, on delègue la
440             # recherche
441             return self.appli.getFile(unite, fic_origine)
442         else:
443             if unite != None:
444                 if os.path.exists("fort." + str(unite)):
445                     fname = "fort." + str(unite)
446             if fname == None:
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')
452             text = fproc.read()
453             fproc.close()
454             text = text.replace('\r\n', '\n')
455             linecache.cache[fname] = 0, 0, text.split('\n'), fname
456             return fname, text
457
458     def set_parLot(self, parLot, user_value=False):
459         """
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
464
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`).
469         """
470         if user_value:
471             self.parLot_user = parLot
472         if self.appli == None:
473             # Pas d application maitre
474             self.parLot = parLot
475         else:
476             # Avec application maitre
477             self.parLot = 'OUI'
478
479     def accept(self, visitor):
480         """
481            Cette methode permet de parcourir l'arborescence des objets
482            en utilisant le pattern VISITEUR
483         """
484         visitor.visitJDC(self)
485
486     def interact(self):
487         """
488             Cette methode a pour fonction d'ouvrir un interpreteur
489             pour que l'utilisateur entre des commandes interactivement
490         """
491         CONTEXT.setCurrentStep(self)
492         try:
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
498             import linecache
499             import code
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)
507         finally:
508             console = None
509             CONTEXT.unsetCurrentStep()
510
511     def getContexteAvant(self, etape):
512         """
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
517         """
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
521         # memorisant l'etape
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()
528             if etape is None:
529                 return d
530             # retirer les sd produites par 'etape'
531             sd_names = [sd.nom for sd in etape.getCreated_sd()]
532             for nom in sd_names:
533                 try:
534                     del d[nom]
535                 except KeyError:
536                     from warnings import warn
537                     warn(
538                         "concept '%s' absent du contexte de %s" % (
539                             nom, self.nom),
540                         RuntimeWarning, stacklevel=2)
541             return d
542         if etape:
543             index_etape = self.index_etapes[etape]
544         else:
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]
552         else:
553             d = self.current_context = {}
554             if self.context_ini:
555                 d.update(self.context_ini)
556             liste_etapes = self.etapes
557
558         for e in liste_etapes:
559             if e is etape:
560                 break
561             if e.isActif():
562                 e.updateContext(d)
563         self.index_etape_courante = index_etape
564         return d
565
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)
575         return d
576
577     def getContexteCourant(self, etape_courante=None):
578         """
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.
581         """
582         if etape_courante is None:
583             etape_courante = CONTEXT.getCurrentStep()
584         return self.getContexteAvant(etape_courante)
585
586     def getConcept(self, nomsd):
587         """
588             Methode pour recuperer un concept a partir de son nom
589         """
590         co = self.getContexteCourant().get(nomsd.strip(), None)
591         if not isinstance(co, ASSD):
592             co = None
593         return co
594
595     def getConceptByType(self, nomsd, typesd, etape):
596         """
597             Methode pour recuperer un concept a partir de son nom et de son type.
598             Il aura comme père 'etape'.
599         """
600         assert issubclass(typesd, ASSD), typesd
601         co = typesd(etape=etape)
602         co.setName(nomsd)
603         co.executed = 1
604         return co
605
606     def del_concept(self, nomsd):
607         """
608            Methode pour supprimer la reference d'un concept dans le sds_dict.
609            Ne detruire pas le concept (different de supprime).
610         """
611         try:
612             del self.sds_dict[nomsd.strip()]
613         except:
614             pass
615
616     def getCmd(self, nomcmd):
617         """
618             Methode pour recuperer la definition d'une commande
619             donnee par son nom dans les catalogues declares
620             au niveau du jdc
621         """
622         for cata in (self.cata,):
623             if hasattr(cata, nomcmd):
624                 return getattr(cata, nomcmd)
625
626     def append_reset(self, etape):
627         """
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
630         """
631         self.etapes.append(etape)
632         self.index_etapes[etape] = len(self.etapes) - 1
633         etape.reparent(self)
634         etape.resetJdc(self)
635
636     def sdAccessible(self):
637         """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
638         """
639         if CONTEXT.debug:
640             print((' `- JDC sdAccessible : PAR_LOT =', self.parLot))
641         return self.parLot == 'NON'
642
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:
648         cat=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'])