1 # -*- coding: utf-8 -*-
2 # CONFIGURATION MANAGEMENT OF EDF VERSION
3 # ======================================================================
4 # COPYRIGHT (C) 1991 - 2002 EDF R&D WWW.CODE-ASTER.ORG
5 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8 # (AT YOUR OPTION) ANY LATER VERSION.
10 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
15 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17 # 1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
20 # ======================================================================
24 escapedQuotesRE = re.compile(r"(\\\\|\\\"|\\\')")
25 stringsAndCommentsRE = \
26 re.compile("(\"\"\".*?\"\"\"|'''.*?'''|\"[^\"]*\"|\'[^\']*\'|#.*?\n)", re.DOTALL)
27 allchars = string.maketrans("", "")
28 allcharsExceptNewline = allchars[: allchars.index('\n')]+allchars[allchars.index('\n')+1:]
29 allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
31 def maskStringsAndComments(src):
32 """Masque tous les caracteres de src contenus dans des commentaires ou des strings multilignes (triples
34 Le masquage est realise en remplacant les caracteres par des *
35 Attention : cette fonction doit etre utilisee sur un texte complet et pas ligne par ligne
37 src = escapedQuotesRE.sub("**", src)
38 allstrings = stringsAndCommentsRE.split(src)
39 # every odd element is a string or comment
40 for i in xrange(1, len(allstrings), 2):
41 if allstrings[i].startswith("'''")or allstrings[i].startswith('"""'):
42 allstrings[i] = allstrings[i][:3]+ \
43 allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \
46 allstrings[i] = allstrings[i][0]+ \
47 allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \
50 return "".join(allstrings)
52 implicitContinuationChars = (('(', ')'), ('[', ']'), ('{', '}'))
53 linecontinueRE = re.compile(r"\\\s*(#.*)?$")
54 emptyHangingBraces = [0,0,0,0,0]
56 class ParserException(Exception): pass
57 class FatalError(Exception): pass
59 #commentaire double precede d'un nombre quelconque de blancs (pas multiligne)
60 pattern_2comments = re.compile(r"^\s*##.*")
61 #commentaire standard precede d'un nombre quelconque de blancs (pas multiligne)
62 pattern_comment = re.compile(r"^\s*#.*")
63 #fin de ligne ; suivi d'un nombre quelconque de blancs (pas multiligne)
64 pattern_fin = re.compile(r"; *$")
65 #pattern pour supprimer les blancs, tabulations et fins de ligne
66 pattern_blancs = re.compile(r"[ \t\r\f\v]")
67 #pattern_blancs = re.compile(r"[\s\n]")
68 number_kw_pattern=re.compile(r"""
70 #groupe nombre decimal
72 #signe : on ignore le signe +
74 #groupe (avec ?: n'apparait pas en tant que groupe dans le resultat)
76 #mantisse forme entiere.fractionnaire
79 #ou forme .fractionnaire
88 """,re.VERBOSE|re.MULTILINE)
90 def construit_genea(texte,liste_mc):
91 """Retourne un dictionnaire dont les cles sont des reels et les valeurs sont leurs representations textuelles.
92 Realise un filtrage sur les reels :
93 - Ne garde que les reels pour lesquels str ne donne pas une bonne representation.
94 - Ne garde que les reels derriere un argument keyword dont le nom est dans liste_mc
95 >>> s = '''a=+21.3e-5*85,b=-.1234,c=81.6 , d= -8 , e=_F(x=342.67,y=-1), f=+1.1, g=(1.3,-5,1.54E-3),
96 ... #POMPE_PRIMA._BOUCLE_N._2_ELEMENT_NUMERO:0239
97 ... h=_F(x=34.6,y=-1)'''
98 >>> construit_genea(s,['a','x'])
103 #on masque les strings et commentaires pour ne pas identifier de faux reels
104 for m in number_kw_pattern.findall(maskStringsAndComments(texte)):
109 if mot not in liste_mc:continue
112 if str(key) != m: d[key]=m
117 """Classe de base pour tous les objets créés lors de la conversion
118 Tout objet dérivé est enregistré auprès de son père à sa création
120 def __init__(self,pere):
122 pere.l_objets.append(self)
124 def set_text(self,texte):
127 def append_text(self,texte):
129 Ajoute texte à self.texte en mettant un retour chariot à la fin de texte
132 self.texte = self.texte +texte
137 class COMMENTAIRE(ENTITE_JDC):
141 Retourne une chaîne de caractères représentants self
142 sous une forme interprétable par EFICAS
145 return "COMMENTAIRE("+t+")\n"
147 #s='COMMENTAIRE("""'+self.texte+'""")\n\n'
150 def append_text(self,texte):
152 Ajoute texte à self.texte en enlevant le # initial
156 self.texte = self.texte+texte[1:]
158 # le dièse n'est pas sur le premier caractère
159 amont,aval = string.split(texte,'#',1) # on découpe suivant la première occurrence de #
160 self.texte = self.texte +amont + aval
162 class COMMANDE(ENTITE_JDC):
168 return self.texte+'\n'
170 def get_nb_par(self):
172 Retourne la différence entre le nombre de parenthèses ouvrantes
173 et le nombre de parenthèses fermantes présentes dans self.texte
174 Peut donc retourner un entier négatif
176 # faire attention aux commentaires contenus dans self.texte
177 # qui peuvent eux-mêmes contenir des parenthèses !!!!
178 l_lignes = string.split(self.texte,'\n')
180 for ligne in l_lignes:
181 ligne = string.split(ligne,'#')[0]
182 nb = nb + (string.count(ligne,'(')-string.count(ligne,')'))
185 class AFFECTATION(ENTITE_JDC):
187 def append_text(self,texte):
189 Ajoute texte à self.texte en enlevant tout retour chariot et tout point virgule
190 PN et tout commentaire
192 if texte[-1] == '\n' : texte = string.rstrip(texte[0:-1])
193 if texte[-1] == ';' : texte = string.rstrip(texte[0:-1])
194 self.texte = self.texte+texte+'\n'
198 Retourne une expression de l'affectation compréhensible par ACCAS
199 et exploitable par EFICAS
201 nom,valeur = string.split(self.texte,'=',1)
202 n = string.rstrip(nom)
203 nom = string.lstrip(n)
204 if valeur[-1] == '\n': valeur = valeur[:-1]
205 return n + ' = PARAMETRE(nom=\''+nom+'\',valeur='+valeur+')\n'
207 class COMMANDE_COMMENTARISEE(ENTITE_JDC):
209 def append_text(self,texte):
211 Ajoute texte à self.texte en enlevant les doubles commentaires
213 texte = string.strip(texte)
214 texte = string.strip(texte[2:])
215 self.texte = self.texte+(len(self.texte)>0)*'\n'+texte
219 Retourne une expression de la commande commentarisée compréhensible par ACCAS
220 et exploitable par EFICAS
222 return "COMMANDE_COMM(texte="+repr(self.texte)+")\n"
223 #return "COMMANDE_COMM(texte='''"+self.texte+"''')\n"
225 class AFFECTATION_EVAL(ENTITE_JDC):
227 def append_text(self,texte):
229 Ajoute texte à self.texte en enlevant tout retour chariot
231 if texte[-1] == '\n' : texte = texte[1:-1]
232 self.texte = self.texte+texte
236 Retourne une expression du paramètre EVAL compréhensible par ACCAS
237 et exploitable par EFICAS
239 nom,valeur = string.split(self.texte,'=',1)
240 nom = string.strip(nom)
241 if valeur[-1] == '\n': valeur = valeur[:-1]
242 valeur = string.strip(valeur)
243 return nom+' = PARAMETRE_EVAL(nom=\''+nom+'\',valeur=\''+valeur+'\')\n\n'
245 class PARSEUR_PYTHON:
247 Cette classe sert à générer un objet PARSEUR_PYTHON qui réalise l'analyse d'un texte
248 représentant un JDC Python en distinguant :
249 - les commentaires inter commandes
253 pattern_commande = re.compile(r'^([A-Z][A-Z0-9_]+)([ \t\r\f\v]*)\(([\w\W]*)')
254 pattern_eval = re.compile(r'^(EVAL)([ \t\r\f\v]*)\(([\w\W]*)')
255 pattern_ligne_vide = re.compile(r'^[\t\r\f\v\n]+')
256 pattern_name = re.compile(r'[a-zA-Z_]\w*')
258 def __init__(self,texte):
263 def is_affectation(self,texte):
265 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation dans un jeu de commandes
268 if '=' not in texte : return 0
269 if self.pattern_commande.match(texte):
270 # cas d'une procédure ...
272 amont,aval = string.split(texte,'=',1)
273 aval = string.strip(aval)
274 if self.pattern_commande.match(aval):
277 s= string.strip(amont)
278 m= self.pattern_name.match(s)
279 if m is None : return 0
280 if m.start() != 0 :return 0
281 if m.end() != len(s):return 0
282 #print texte,amont,aval
285 def is_eval(self,texte):
287 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation de type EVAL
288 dans un jeu de commandes Aster, 0 sinon
290 if '=' not in texte : return 0
291 if self.pattern_commande.match(texte):
292 # cas d'une procédure ...
294 amont,aval = string.split(texte,'=',1)
295 aval = string.strip(aval)
296 if not self.pattern_commande.match(aval) : return 0
297 if self.pattern_eval.match(aval):
302 def is_commande(self,texte):
304 Méthode booléenne qui retourne 1 si le texte est celui d'une commande dans un jeu de commandes
307 if self.pattern_commande.match(texte):
308 # cas d'une procédure ...
310 # A ce stade il faut avoir un OPER ou une MACRO, bref un '=' !
311 if '=' not in texte : return 0
312 # on a un texte de la forme xxxx = yyyyy
313 # --> reste à analyser yyyy
314 amont,aval = string.split(texte,'=',1)
315 aval = string.strip(aval)
316 if self.pattern_commande.match(aval):
323 Eclate la chaine self.texte en self.l_objets une liste lignes d'instructions
324 et de commentaires (parmi lesquels des instructions "commentarisées").
326 l_lignes = string.split(self.texte,'\n')
327 commentaire_courant = None
328 commande_courante = None
329 affectation_courante = None
330 commande_commentarisee_courante = None
333 #initialisation du nombre de parentheses non fermees et de commentaires non termines
334 #Attention a reinitialiser en fin de ligne logique
335 #Une ligne logique peut s'etendre sur plusieurs lignes physiques avec des caracteres de continuation
336 #explicites ou implicites
337 hangingBraces = list(emptyHangingBraces)
340 #Masquage des commentaires et strings multilignes
341 srcMasked=maskStringsAndComments('\n'.join(l_lignes))
343 masked_lines=srcMasked.split('\n')
346 for ligne in l_lignes :
347 line=masked_lines[lineno]
350 # mise a jour du nombre total de parentheses ouvertes (non fermees)
351 # et du nombre de commentaires non termines
352 for i in range(len(implicitContinuationChars)):
353 contchar = implicitContinuationChars[i]
354 numHanging = hangingBraces[i]
355 hangingBraces[i] = numHanging+line.count(contchar[0]) - line.count(contchar[1])
357 hangingComments ^= line.count('"""') % 2
358 hangingComments ^= line.count("'''") % 2
359 #print hangingComments,hangingBraces
360 if hangingBraces[0] < 0 or hangingBraces[1] < 0 or hangingBraces[2] < 0:
361 raise ParserException()
363 if string.strip(ligne) == '':
364 # il s'agit d'un saut de ligne
368 if pattern_2comments.match(ligne):
369 #on a trouvé une commande commentarisée : double commentaire sans rien devant à part des blancs
370 if commentaire_courant:
371 #Si un commentaire ordinaire est en cours on le termine
372 commentaire_courant = None
374 if commande_courante :
375 # on a un objet commentarisé à l'intérieur d'une commande
376 # --> non traité pour l'instant : on l'ajoute simplement a la commande courante comme
377 # un commentaire ordinaire
378 commande_courante.append_text(ligne)
379 elif commande_commentarisee_courante :
380 # commande_commentarisee en cours : on ajoute la ligne
381 commande_commentarisee_courante.append_text(ligne)
383 # debut de commande commentarisée : on crée un objet commande_commentarisee_courante
384 commande_commentarisee_courante = COMMANDE_COMMENTARISEE(self)
385 commande_commentarisee_courante.append_text(ligne)
387 #on passe à la ligne suivante
390 if pattern_comment.match(ligne):
391 #commentaire ordinaire avec seulement des blancs devant
392 if commande_commentarisee_courante :
393 # commande_commentarisee en cours : on la clot
394 commande_commentarisee_courante = None
396 if commande_courante :
397 # il s'agit d'un commentaire à l'intérieur d'une commande --> on ne fait rien de special
398 #on l'ajoute au texte de la commande
399 commande_courante.append_text(ligne)
400 elif commentaire_courant :
401 # il s'agit de la nième ligne d'un commentaire entre deux commandes
402 # --> on ajoute cette ligne au commentaire courant
403 commentaire_courant.append_text(ligne)
405 # il s'agit d'un nouveau commentaire entre deux commandes
406 # --> on le crée et il devient le commentaire courant
407 commentaire_courant = COMMENTAIRE(self)
408 commentaire_courant.append_text(ligne)
410 #on passe à la ligne suivante
413 # la ligne contient des données autre qu'un éventuel commentaire
414 if commentaire_courant :
415 # on clôt un éventuel commentaire courant
416 commentaire_courant = None
418 if commande_commentarisee_courante :
419 # on clôt une éventuelle commande commentarisee courante
420 commande_commentarisee_courante = None
422 if commande_courante :
423 #on a une commande en cours. On l'enrichit ou on la termine
424 commande_courante.append_text(ligne)
425 if not linecontinueRE.search(line) \
426 and (hangingBraces == emptyHangingBraces) \
427 and not hangingComments:
428 #la commande est terminée
429 #print "fin de commande"
430 self.analyse_reel(commande_courante.texte)
431 commande_courante = None
433 #on passe à la ligne suivante
436 if affectation_courante != None :
437 #poursuite d'une affectation
438 affectation_courante.append_text(ligne)
439 if not linecontinueRE.search(line) \
440 and (hangingBraces == emptyHangingBraces) \
441 and not hangingComments:
442 #L'affectation est terminée
443 affectation_courante=None
444 #on passe à la ligne suivante
447 # il peut s'agir d'une commande ou d'une affectation ...
449 if self.is_eval(ligne):
450 # --> affectation de type EVAL
451 if affectation_courante : affectation_courante = None
452 affectation = AFFECTATION_EVAL(self)
453 affectation.append_text(ligne)
454 #on passe à la ligne suivante
457 if self.is_affectation(ligne):
460 #traitement des commentaires en fin de ligne
461 compos=line.find("#")
463 #commentaire en fin de ligne
464 #on cree un nouveau commentaire avant le parametre
465 COMMENTAIRE(self).append_text(ligne[compos:])
467 #si plusieurs instructions separees par des ; sur la meme ligne
468 inspos=line.find(";")
470 #on garde seulement la premiere partie de la ligne
471 #si on a que des blancs apres le point virgule
472 if string.strip(text[inspos:]) == ";":
475 raise FatalError("Eficas ne peut pas traiter plusieurs instructions sur la meme ligne : %s" % ligne)
477 affectation_courante = AFFECTATION(self)
478 affectation_courante.append_text(text)
479 if not linecontinueRE.search(line) \
480 and (hangingBraces == emptyHangingBraces) \
481 and not hangingComments:
482 #L'affectation est terminée
483 affectation_courante=None
484 #on passe à la ligne suivante
487 if self.is_commande(ligne):
488 # --> nouvelle commande
489 affectation_courante = None
490 commande_courante = COMMANDE(self)
491 commande_courante.append_text(ligne)
492 #si la commande est complète, on la termine
493 if not linecontinueRE.search(line) \
494 and (hangingBraces == emptyHangingBraces) \
495 and not hangingComments:
496 #la commande est terminée
497 #print "fin de commande"
498 self.analyse_reel(commande_courante.texte)
499 commande_courante = None
500 #on passe à la ligne suivante
503 def enleve (self,texte) :
504 """Supprime de texte tous les caracteres blancs, fins de ligne, tabulations
505 Le nouveau texte est retourné
509 while (i<len(texte)):
510 if (texte[i] == " " or texte[i] == "\n" or texte[i] == "\t") :
513 chaine=chaine+texte[i]
517 def construit_genea(self,texte):
522 # traitement pour chaque caractere
523 while (indiceC < len(texte)):
525 if ( c == "," or c == "(" or c == ")"):
528 #on doit trouver derriere soit une valeur soit une parenthese
530 nouvelindice=indiceC+1
531 if texte[nouvelindice] != "(":
532 #pas de parenthese ouvrante derriere un signe =, on a une valeur.
533 while ( texte[nouvelindice] != "," and texte[nouvelindice] != ")"):
534 valeur=valeur+texte[nouvelindice]
535 nouvelindice=nouvelindice+1
536 if nouvelindice == len(texte) :
537 nouvelindice=nouvelindice -1
539 if mot in self.appli.liste_simp_reel:
543 if str(clef) != str(valeur) :
544 dict_reel_concept[clef]=valeur
550 #parenthese ouvrante derriere un signe =, on a un tuple de valeur ou de mots cles facteurs.
551 # s agit -il d un tuple
552 if texte[nouvelindice+1] != "(":
553 #le suivant n'est pas une parenthese ouvrante : on a un tuple de valeurs ou un mot cle facteur
555 #on avance jusqu'a la fin du tuple de valeurs ou jusqu'a la fin du premier mot cle simple
556 #contenu dans le mot cle facteur
557 while ( texte[nouvelindice] != "="):
558 if texte[nouvelindice] == ")" :
562 nouvelindice=nouvelindice+1
563 if nouvelindice == len(texte) :
564 nouvelindice=nouvelindice -1
567 #cas du tuple de valeurs
568 valeur=texte[indiceC+1:nouvelindice+1]
569 indiceC=nouvelindice+1
570 if mot in self.appli.liste_simp_reel:
572 for val in valeur.split(',') :
573 # Attention la derniere valeur est""
577 if str(clef) != str(val) :
578 dict_reel_concept[clef]=val
584 #cas du mocle facteur simple ou
587 mot=mot+texte[indiceC]
589 # traitement du dernier inutile
591 return dict_reel_concept
593 def analyse_reel(self,commande) :
595 # On verifie qu on a bien un OPER
597 if commande.find("=") > commande.find("(") :
599 if commande.find("=") > 0:
600 #epure1=self.enleve(commande)
601 epure1=pattern_blancs.sub("",commande)
602 nomConcept,corps=epure1.split("=",1)
603 epure2=corps.replace("_F(","(")
604 #nomConcept=epure1.split("=")[0]
605 #index=epure1.find("=")
606 #epure2=epure1[index+1:len(epure1)].replace("_F(","(")
607 #dict_reel_concept=self.construit_genea(epure2)
608 dict_reel_concept=construit_genea(epure2,self.appli.liste_simp_reel)
609 if nomConcept !=None :
610 if len(dict_reel_concept) != 0:
611 self.appli.dict_reels[nomConcept]=dict_reel_concept
613 def get_texte(self,appli=None):
615 Retourne le texte issu de l'analyse
619 if not self.l_objets : self.analyse()
621 for obj in self.l_objets:
623 except ParserException:
624 #Impossible de convertir le texte, on le retourne tel que
629 import parseur_python
631 doctest.testmod(parseur_python)
634 if __name__ == "__main__" :
636 #fichier = 'D:/Eficas_dev/Tests/zzzz100a.comm'
637 fichier = 'U:/Eficas_dev/Tests/test_eval.comm'
638 fichier = '/local/chris/ASTER/Eficas/Eficas1_10/EficasV1/Tests/testcomm/b.comm'
639 fichier = '/local/chris/ASTER/instals/STA8.2/astest/forma12c.comm'
640 fichier = 'titi.comm'
641 fichier = '../Aster/sdls300a.comm'
642 texte = open(fichier,'r').read()
645 liste_simp_reel=["VALE","VALE_C","GROUP_MA","RAYON"]
650 txt = PARSEUR_PYTHON(texte).get_texte(a)
651 print t0,time.clock()-t0
653 import hotshot, hotshot.stats
654 prof = hotshot.Profile("stones.prof")
655 txt = prof.runcall(PARSEUR_PYTHON(texte).get_texte,a)
657 stats = hotshot.stats.load("stones.prof")
659 stats.sort_stats('time', 'calls')
660 stats.print_stats(20)
663 compile(txt, '<string>', 'exec')