Salome HOME
gestion des listes et label sur 2
[tools/eficas.git] / Noyau / N_JDC.py
1 # coding=utf-8
2 # Copyright (C) 2007-2013   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 à interpréter un jeu de commandes
23 """
24
25 # Modules Python
26 import os
27 import string
28 import traceback
29 import types
30 import sys
31 import linecache
32
33 # Modules EFICAS
34 import N_OBJECT
35 import N_CR
36 from N_Exception import AsException, InterruptParsingError
37 from N_ASSD import ASSD
38 from N_info import message, SUPERV
39 from strfunc import get_encoding
40
41
42 MemoryErrorMsg = """MemoryError :
43
44 En général, cette erreur se produit car la mémoire utilisée hors du fortran
45 (jeveux) est importante.
46
47 Causes possibles :
48    - le calcul produit de gros objets Python dans une macro-commande ou
49      dans le jeu de commande lui-même,
50    - le calcul appelle un solveur (MUMPS par exemple) ou un outil externe
51      qui a besoin de mémoire hors jeveux,
52    - utilisation de jeveux dynamique,
53    - ...
54
55 Solution :
56    - distinguer la mémoire limite du calcul (case "Mémoire totale" de astk)
57      de la mémoire réservée à jeveux (case "dont Aster"), le reste étant
58      disponible pour les allocations dynamiques.
59 """
60
61
62 class JDC(N_OBJECT.OBJECT):
63
64     """
65        Cette classe interprete un jeu de commandes fourni sous
66        la forme d'une chaine de caractères
67
68        Attributs de classe :
69
70        Attributs d'instance :
71
72     """
73     nature = "JDC"
74     CR = N_CR.CR
75     exec_init = """
76 import Accas
77 from Accas import _F
78 from Accas import *
79 NONE = None
80 """
81
82     from N_utils import SEP
83
84     def __init__(self, definition=None, procedure=None, cata=None,
85                  cata_ord_dico=None, parent=None,
86                  nom='SansNom', appli=None, context_ini=None, **args):
87         self.procedure = procedure
88         self.definition = definition
89         self.cata = cata
90         if type(self.cata) != types.TupleType and cata != None:
91             self.cata = (self.cata,)
92         self._build_reserved_kw_list()
93         self.cata_ordonne_dico = cata_ord_dico
94         self.nom = nom
95         self.appli = appli
96         self.parent = parent
97         self.context_ini = context_ini
98         # On conserve les arguments supplémentaires. Il est possible de passer
99         # des informations globales au JDC par ce moyen. Il pourrait etre plus
100         # sur de mettre en place le mecanisme des mots-cles pour verifier la
101         # validité des valeurs passées.
102         # Ceci reste à faire
103         # On initialise avec les parametres de la definition puis on
104         # update avec ceux du JDC
105         self.args = self.definition.args
106         self.args.update(args)
107         self.nstep = 0
108         self.nsd = 0
109         self.par_lot = 'OUI'
110         self.par_lot_user = None
111         if definition:
112             self.regles = definition.regles
113             self.code = definition.code
114         else:
115             self.regles = ()
116             self.code = "CODE"
117         #
118         #  Creation de l objet compte rendu pour collecte des erreurs
119         #
120         self.cr = self.CR(debut="CR phase d'initialisation",
121                           fin="fin CR phase d'initialisation")
122         # on met le jdc lui-meme dans le context global pour l'avoir sous
123         # l'etiquette "jdc" dans le fichier de commandes
124         self.g_context = {'jdc': self}
125         # message.debug(SUPERV, "g_context : %s - %s", self.g_context, id(self.g_context))
126         # Dictionnaire pour stocker tous les concepts du JDC (acces rapide par
127         # le nom)
128         self.sds_dict = {}
129         self.etapes = []
130         self.index_etapes = {}
131         self.mc_globaux = {}
132         self.current_context = {}
133         self.condition_context = {}
134         self.index_etape_courante = 0
135         self.UserError = "UserError"
136         self.alea = None
137         # permet transitoirement de conserver la liste des étapes
138         self.hist_etape = False
139
140     def compile(self):
141         """
142            Cette methode compile la chaine procedure
143            Si des erreurs se produisent, elles sont consignées dans le
144            compte-rendu self.cr
145         """
146         try:
147             if self.appli != None:
148                 self.appli.affiche_infos(
149                     'Compilation du fichier de commandes en cours ...')
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(get_encoding())
154             self.proc_compile = compile(
155                 self.procedure, encoded_filename, 'exec')
156         except SyntaxError, e:
157             if CONTEXT.debug:
158                 traceback.print_exc()
159             l = traceback.format_exception_only(SyntaxError, e)
160             self.cr.exception("Compilation impossible : " + string.join(l))
161         except MemoryError, e:
162             self.cr.exception(MemoryErrorMsg)
163         except SystemError, e:
164             erreurs_connues = """
165 Causes possibles :
166  - offset too large : liste trop longue derrière un mot-clé.
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 exec_compile(self):
176         """
177            Cette méthode execute le jeu de commandes compilé dans le contexte
178            self.g_context de l'objet JDC
179         """
180         CONTEXT.set_current_step(self)
181         # Le module nommage utilise le module linecache pour accéder
182         # au source des commandes du jeu de commandes.
183         # Dans le cas d'un fichier, on accède au contenu de ce fichier
184         # Dans le cas d'une chaine de caractères il faut accéder
185         # aux commandes qui sont dans la chaine
186         import linecache
187         linecache.cache[self.nom] = 0, 0, string.split(
188             self.procedure, '\n'), self.nom
189         try:
190             exec self.exec_init in self.g_context
191             # message.debug(SUPERV, "JDC.exec_compile_1 - len(g_context) = %d",
192             # len(self.g_context.keys()))
193             for obj_cata in self.cata:
194                 if type(obj_cata) == types.ModuleType:
195                     init2 = "from " + obj_cata.__name__ + " import *"
196                     exec init2 in self.g_context
197             # message.debug(SUPERV, "JDC.exec_compile_2 - len(g_context) = %d",
198             # len(self.g_context.keys()))
199
200             # Initialisation du contexte global pour l'évaluation des conditions de BLOC
201             # On utilise une copie de l'initialisation du contexte du jdc
202             self.condition_context = self.g_context.copy()
203
204             # Si l'attribut context_ini n'est pas vide, on ajoute au contexte global
205             # le contexte initial (--> permet d'évaluer un JDC en récupérant un contexte
206             # d'un autre par exemple)
207             if self.context_ini:
208                 self.g_context.update(self.context_ini)
209                 # Update du dictionnaire des concepts
210                 for sdnom, sd in self.context_ini.items():
211                     if isinstance(sd, ASSD):
212                         self.sds_dict[sdnom] = sd
213
214             if self.appli != None:
215                 self.appli.affiche_infos(
216                     'Interprétation du fichier de commandes en cours ...')
217             # On sauve le contexte pour garder la memoire des constantes
218             # En mode edition (EFICAS) ou lors des verifications le contexte
219             # est recalculé
220             # mais les constantes sont perdues
221             self.const_context = self.g_context
222             # message.debug(SUPERV, "pass")
223             exec self.proc_compile in self.g_context
224             # message.debug(SUPERV, "JDC.exec_compile_3 - len(g_context) = %d",
225             # len(self.g_context.keys()))
226
227             CONTEXT.unset_current_step()
228             if self.appli != None:
229                 self.appli.affiche_infos('')
230
231         except InterruptParsingError:
232             # interrupt the command file parsing used by FIN to ignore the end
233             # of the file
234             pass
235
236         except EOFError:
237             # Exception utilise pour interrompre un jeu
238             # de commandes avant la fin
239             # Fonctionnement normal, ne doit pas etre considere comme une
240             # erreur
241             CONTEXT.unset_current_step()
242             self.affiche_fin_exec()
243             self.traiter_fin_exec('commande')
244
245         except AsException, e:
246             # une erreur a ete identifiee
247             if CONTEXT.debug:
248                 traceback.print_exc()
249             # l'exception a été récupérée avant (où, comment ?),
250             # donc on cherche dans le texte
251             txt = str(e)
252             if txt.find('MemoryError') >= 0:
253                 txt = MemoryErrorMsg
254             self.cr.exception(txt)
255             CONTEXT.unset_current_step()
256
257         except NameError, e:
258             etype, value, tb = sys.exc_info()
259             l = traceback.extract_tb(tb)
260             s = traceback.format_exception_only("Erreur de nom", e)[0][:-1]
261             msg = "erreur de syntaxe,  %s ligne %d" % (s, l[-1][1])
262             if CONTEXT.debug:
263                 traceback.print_exc()
264             self.cr.exception(msg)
265             CONTEXT.unset_current_step()
266
267         except self.UserError, exc_val:
268             self.traiter_user_exception(exc_val)
269             CONTEXT.unset_current_step()
270             self.affiche_fin_exec()
271             self.traiter_fin_exec('commande')
272
273         except:
274             # erreur inattendue
275             # sys_exc_typ,sys_exc_value,sys_exc_frame = sys_exc.info()
276             # (tuple de 3 éléments)
277             if CONTEXT.debug:
278                 traceback.print_exc()
279
280             traceback.print_exc()
281
282             exc_typ, exc_val, exc_fr = sys.exc_info()
283             l = traceback.format_exception(exc_typ, exc_val, exc_fr)
284             self.cr.exception(
285                 "erreur non prevue et non traitee prevenir la maintenance " + '\n' + string.join(l))
286             del exc_typ, exc_val, exc_fr
287             CONTEXT.unset_current_step()
288
289     def affiche_fin_exec(self):
290         """
291            Cette methode realise l'affichage final des statistiques de temps
292            apres l'execution de toutes
293            les commandes en mode commande par commande ou par lot
294            Elle doit etre surchargee pour en introduire un
295         """
296         return
297
298     def traiter_fin_exec(self, mode, etape=None):
299         """
300            Cette methode realise un traitement final apres l'execution de toutes
301            les commandes en mode commande par commande ou par lot
302            Par defaut il n'y a pas de traitement. Elle doit etre surchargee
303            pour en introduire un
304         """
305         message.info(SUPERV, "FIN D'EXECUTION %s %s", mode, etape)
306
307     def traiter_user_exception(self, exc_val):
308         """Cette methode realise un traitement sur les exceptions utilisateur
309            Par defaut il n'y a pas de traitement. La méthode doit etre
310            surchargée pour en introduire un.
311         """
312         return
313
314     def register(self, etape):
315         """
316            Cette méthode ajoute etape dans la liste des etapes : self.etapes
317            et retourne un numéro d'enregistrement
318         """
319         self.etapes.append(etape)
320         self.index_etapes[etape] = len(self.etapes) - 1
321         # message.debug(SUPERV, "#%d %s", self.index_etapes[etape], etape.nom)
322         return self.g_register(etape)
323
324     def o_register(self, sd):
325         """
326            Retourne un identificateur pour concept
327         """
328         self.nsd = self.nsd + 1
329         nom = sd.idracine + self.SEP + `self.nsd`
330         return nom
331
332     def g_register(self, etape):
333         """
334             Retourne un identificateur pour etape
335         """
336         self.nstep = self.nstep + 1
337         idetape = etape.idracine + self.SEP + `self.nstep`
338         return idetape
339
340     def create_sdprod(self, etape, nomsd):
341         """
342             Cette methode doit fabriquer le concept produit retourne
343             par l'etape etape et le nommer.
344
345             Elle est appelée à l'initiative de l'etape
346             pendant le processus de construction de cette etape :
347             methode __call__ de la classe CMD (OPER ou MACRO)
348
349             Ce travail est réalisé par le contexte supérieur
350             (etape.parent) car dans certains cas, le concept ne doit
351             pas etre fabriqué mais l'etape doit simplement utiliser
352             un concept préexistant.
353
354             Deux cas possibles :
355                     - Cas 1 : etape.reuse != None : le concept est réutilisé
356                     - Cas 2 : l'étape appartient à une macro qui a déclaré un
357                             concept de sortie qui doit etre produit par cette
358                             etape.
359             Dans le cas du JDC, le deuxième cas ne peut pas se produire.
360         """
361         sd = etape.get_sd_prod()
362         if sd != None and (etape.definition.reentrant == 'n' or etape.reuse is None):
363             # ATTENTION : On ne nomme la SD que dans le cas de non reutilisation
364             # d un concept. Commande non reentrante ou reuse absent.
365             self.NommerSdprod(sd, nomsd)
366         return sd
367
368     def NommerSdprod(self, sd, sdnom, restrict='non'):
369         """
370             Nomme la SD apres avoir verifie que le nommage est possible : nom
371             non utilise
372             Si le nom est deja utilise, leve une exception
373             Met le concept créé dans le concept global g_context
374         """
375         o = self.sds_dict.get(sdnom, None)
376         if isinstance(o, ASSD):
377             raise AsException("Nom de concept deja defini : %s" % sdnom)
378         if sdnom in self._reserved_kw:
379             raise AsException(
380                 "Nom de concept invalide. '%s' est un mot-clé réservé." % sdnom)
381
382         # Ajoute a la creation (appel de reg_sd).
383         self.sds_dict[sdnom] = sd
384         sd.set_name(sdnom)
385
386         # En plus si restrict vaut 'non', on insere le concept dans le contexte
387         # du JDC
388         if restrict == 'non':
389             self.g_context[sdnom] = sd
390             # message.debug(SUPERV, "g_context[%r] = %s", sdnom, sd)
391
392     def reg_sd(self, sd):
393         """
394             Methode appelee dans l __init__ d un ASSD lors de sa creation
395             pour s enregistrer
396         """
397         return self.o_register(sd)
398
399     def delete_concept_after_etape(self, etape, sd):
400         """
401             Met à jour les étapes du JDC qui sont après etape suite à
402             la disparition du concept sd
403         """
404         # Cette methode est définie dans le noyau mais ne sert que pendant
405         # la phase de creation des etapes et des concepts. Il n'y a aucun
406         # traitement particulier à réaliser.
407         # Dans d'autres conditions, il faut surcharger cette méthode
408         return
409
410     def supprime(self):
411         N_OBJECT.OBJECT.supprime(self)
412         for etape in self.etapes:
413             etape.supprime()
414
415     def clean(self, netapes):
416         """Nettoie les `netapes` dernières étapes de la liste des étapes."""
417         if self.hist_etape:
418             return
419         for i in xrange(netapes):
420             e = self.etapes.pop()
421             jdc = e.jdc
422             parent = e.parent
423             e.supprime()
424             e.parent = parent
425             e.jdc = jdc
426             # message.debug(SUPERV, "JDC.clean - etape = %r - refcount(e) = %d",
427                           # e.nom, sys.getrefcount(e))
428             del self.index_etapes[e]
429
430     def get_file(self, unite=None, fic_origine='', fname=None):
431         """
432             Retourne le nom du fichier correspondant à un numero d'unité
433             logique (entier) ainsi que le source contenu dans le fichier
434         """
435         if self.appli:
436             # Si le JDC est relié à une application maitre, on délègue la
437             # recherche
438             return self.appli.get_file(unite, fic_origine)
439         else:
440             if unite != None:
441                 if os.path.exists("fort." + str(unite)):
442                     fname = "fort." + str(unite)
443             if fname == None:
444                 raise AsException("Impossible de trouver le fichier correspondant"
445                                   " a l unite %s" % unite)
446             if not os.path.exists(fname):
447                 raise AsException("%s n'est pas un fichier existant" % fname)
448             fproc = open(fname, 'r')
449             text = fproc.read()
450             fproc.close()
451             text = text.replace('\r\n', '\n')
452             linecache.cache[fname] = 0, 0, text.split('\n'), fname
453             return fname, text
454
455     def set_par_lot(self, par_lot, user_value=False):
456         """
457         Met le mode de traitement a PAR LOT
458         ou a COMMANDE par COMMANDE
459         en fonction de la valeur du mot cle PAR_LOT et
460         du contexte : application maitre ou pas
461
462         En PAR_LOT='NON', il n'y a pas d'ambiguité.
463         En PAR_LOT='OUI', E_SUPERV positionne l'attribut à 'NON' après la phase
464         d'analyse et juste avant la phase d'exécution.
465         `user_value` : permet de stocker la valeur choisie par l'utilisateur
466         pour l'interroger plus tard (par exemple dans `get_contexte_avant`).
467         """
468         # message.debug(SUPERV, "set par_lot = %r", par_lot)
469         if user_value:
470             self.par_lot_user = par_lot
471         if self.appli == None:
472             # Pas d application maitre
473             self.par_lot = par_lot
474         else:
475             # Avec application maitre
476             self.par_lot = 'OUI'
477
478     def accept(self, visitor):
479         """
480            Cette methode permet de parcourir l'arborescence des objets
481            en utilisant le pattern VISITEUR
482         """
483         visitor.visitJDC(self)
484
485     def interact(self):
486         """
487             Cette methode a pour fonction d'ouvrir un interpreteur
488             pour que l'utilisateur entre des commandes interactivement
489         """
490         CONTEXT.set_current_step(self)
491         try:
492             # Le module nommage utilise le module linecache pour accéder
493             # au source des commandes du jeu de commandes.
494             # Dans le cas d'un fichier, on accède au contenu de ce fichier
495             # Dans le cas de la console interactive, il faut pouvoir accéder
496             # aux commandes qui sont dans le buffer de la console
497             import linecache
498             import code
499             console = code.InteractiveConsole(
500                 self.g_context, filename="<console>")
501             linecache.cache["<console>"] = 0, 0, console.buffer, "<console>"
502             banner = """***********************************************
503 *          Interpreteur interactif %s
504 ***********************************************""" % self.code
505             console.interact(banner)
506         finally:
507             console = None
508             CONTEXT.unset_current_step()
509
510     def get_contexte_avant(self, etape):
511         """
512            Retourne le dictionnaire des concepts connus avant etape
513            On tient compte des commandes qui modifient le contexte
514            comme DETRUIRE ou les macros
515            Si etape == None, on retourne le contexte en fin de JDC
516         """
517         # L'étape courante pour laquelle le contexte a été calculé est
518         # mémorisée dans self.index_etape_courante
519         # XXX on pourrait faire mieux dans le cas PAR_LOT="NON" : en
520         # mémorisant l'étape
521         # courante pendant le processus de construction des étapes.
522         # Si on insère des commandes (par ex, dans EFICAS), il faut préalablement
523         # remettre ce pointeur à 0
524         # message.debug(SUPERV, "g_context : %s", [k for k, v in self.g_context.items() if isinstance(v, ASSD)])
525         # message.debug(SUPERV, "current_context : %s", [k for k, v in
526         # self.current_context.items() if isinstance(v, ASSD)])
527         if self.par_lot_user == 'NON':
528             d = self.current_context = self.g_context.copy()
529             if etape is None:
530                 return d
531             # retirer les sd produites par 'etape'
532             sd_names = [sd.nom for sd in etape.get_created_sd()]
533             # message.debug(SUPERV, "reuse : %s, sdprods : %s", etape.reuse,
534             # sd_names)
535             for nom in sd_names:
536                 try:
537                     del d[nom]
538                 except KeyError:
539                     from warnings import warn
540                     warn(
541                         "concept '%s' absent du contexte de %s" % (
542                             nom, self.nom),
543                         RuntimeWarning, stacklevel=2)
544             return d
545         if etape:
546             index_etape = self.index_etapes[etape]
547         else:
548             index_etape = len(self.etapes)
549         if index_etape >= self.index_etape_courante:
550             # On calcule le contexte en partant du contexte existant
551             d = self.current_context
552             if self.index_etape_courante == 0 and self.context_ini:
553                 d.update(self.context_ini)
554             liste_etapes = self.etapes[self.index_etape_courante:index_etape]
555         else:
556             d = self.current_context = {}
557             if self.context_ini:
558                 d.update(self.context_ini)
559             liste_etapes = self.etapes
560
561         for e in liste_etapes:
562             if e is etape:
563                 break
564             if e.isactif():
565                 e.update_context(d)
566         self.index_etape_courante = index_etape
567         # message.debug(SUPERV, "returns : %s", [k for k, v in d.items() if
568         # isinstance(v, ASSD)])
569         return d
570
571     def get_global_contexte(self):
572         """Retourne "un" contexte global ;-)"""
573         # N'est utilisé que par INCLUDE (sauf erreur).
574         # g_context est remis à {} en PAR_LOT='OUI'. const_context permet
575         # de retrouver ce qui y a été mis par exec_compile.
576         # Les concepts n'y sont pas en PAR_LOT='OUI'. Ils sont ajoutés
577         # par get_global_contexte de la MACRO.
578         d = self.const_context.copy()
579         d.update(self.g_context)
580         return d
581
582     def get_contexte_courant(self, etape_courante=None):
583         """
584            Retourne le contexte tel qu'il est (ou 'sera' si on est en phase
585            de construction) au moment de l'exécution de l'étape courante.
586         """
587         if etape_courante is None:
588             etape_courante = CONTEXT.get_current_step()
589         return self.get_contexte_avant(etape_courante)
590
591     def get_concept(self, nomsd):
592         """
593             Méthode pour récuperer un concept à partir de son nom
594         """
595         co = self.get_contexte_courant().get(nomsd.strip(), None)
596         if not isinstance(co, ASSD):
597             co = None
598         return co
599
600     def get_concept_by_type(self, nomsd, typesd, etape):
601         """
602             Méthode pour récuperer un concept à partir de son nom et de son type.
603             Il aura comme père 'etape'.
604         """
605         assert issubclass(typesd, ASSD), typesd
606         co = typesd(etape=etape)
607         co.set_name(nomsd)
608         co.executed = 1
609         return co
610
611     def del_concept(self, nomsd):
612         """
613            Méthode pour supprimer la référence d'un concept dans le sds_dict.
614            Ne détruire pas le concept (différent de supprime).
615         """
616         try:
617             del self.sds_dict[nomsd.strip()]
618         except:
619             pass
620
621     def get_cmd(self, nomcmd):
622         """
623             Méthode pour recuperer la definition d'une commande
624             donnee par son nom dans les catalogues declares
625             au niveau du jdc
626         """
627         for cata in self.cata:
628             if hasattr(cata, nomcmd):
629                 return getattr(cata, nomcmd)
630
631     def append_reset(self, etape):
632         """
633            Ajoute une etape provenant d'un autre jdc a la liste des etapes
634            et remet à jour la parenté de l'étape et des concepts
635         """
636         self.etapes.append(etape)
637         self.index_etapes[etape] = len(self.etapes) - 1
638         etape.reparent(self)
639         etape.reset_jdc(self)
640
641     def sd_accessible(self):
642         """On peut acceder aux "valeurs" (jeveux) des ASSD si le JDC est en PAR_LOT="NON".
643         """
644         if CONTEXT.debug:
645             print ' `- JDC sd_accessible : PAR_LOT =', self.par_lot
646         return self.par_lot == 'NON'
647
648     def _build_reserved_kw_list(self):
649         """Construit la liste des mots-clés réservés (interdits pour le
650         nommage des concepts)."""
651         self._reserved_kw = set()
652         for cat in self.cata:
653             self._reserved_kw.update(
654                 [kw for kw in dir(cat) if len(kw) <= 8 and kw == kw.upper()])
655         self._reserved_kw.difference_update(
656             ['OPER', 'MACRO', 'BLOC', 'SIMP', 'FACT', 'FORM',
657              'GEOM', 'MCSIMP', 'MCFACT'])