1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2013 EDF R&D
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 from Extensions.i18n import tr
24 escapedQuotesRE = re.compile(r"(\\\\|\\\"|\\\')")
25 stringsAndCommentsRE = \
26 re.compile(u"(\"\"\".*?\"\"\"|'''.*?'''|\"[^\"]*\"|\'[^\']*\'|#.*?\n)", re.DOTALL)
27 allchars = string.maketrans(u"", "")
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(u"**", 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(u"'''")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 pattern_finComments = re.compile("^\s*##Fin Commentaire")
62 #commentaire standard precede d'un nombre quelconque de blancs (pas multiligne)
63 pattern_comment = re.compile(r"^\s*#.*")
64 #fin de ligne ; suivi d'un nombre quelconque de blancs (pas multiligne)
65 pattern_fin = re.compile(r"; *$")
66 #pattern pour supprimer les blancs, tabulations et fins de ligne
67 pattern_blancs = re.compile(r"[ \t\r\f\v]")
68 #pattern_blancs = re.compile(r"[\s\n]")
69 number_kw_pattern=re.compile(r"""
71 #groupe nombre decimal
73 #signe : on ignore le signe +
75 #groupe (avec ?: n'apparait pas en tant que groupe dans le resultat)
77 #mantisse forme entiere.fractionnaire
80 #ou forme .fractionnaire
89 """,re.VERBOSE|re.MULTILINE)
91 def construit_genea(texte,liste_mc):
93 Retourne un dictionnaire dont les cles sont des reels et les valeurs sont leurs representations textuelles.
95 Realise un filtrage sur les reels :
97 - Ne garde que les reels pour lesquels str ne donne pas une bonne representation.
98 - Ne garde que les reels derriere un argument keyword dont le nom est dans liste_mc
100 >>> 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),
101 ... #POMPE_PRIMA._BOUCLE_N._2_ELEMENT_NUMERO:0239
102 ... h=_F(x=34.6,y=-1)'''
103 >>> construit_genea(s,['a','x'])
104 {0.000213: '21.3e-5'}
108 #on masque les strings et commentaires pour ne pas identifier de faux reels
109 for m in number_kw_pattern.findall(maskStringsAndComments(texte)):
114 if mot not in liste_mc:continue
117 if str(key) != m: d[key]=m
122 """Classe de base pour tous les objets créés lors de la conversion
123 Tout objet dérivé est enregistré auprès de son pere a sa création
125 def __init__(self,pere):
127 pere.l_objets.append(self)
129 def set_text(self,texte):
132 def append_text(self,texte):
134 Ajoute texte a self.texte en mettant un retour chariot a la fin de texte
137 self.texte = self.texte +texte
142 class COMMENTAIRE(ENTITE_JDC):
146 Retourne une chaine de caractères représentants self
147 sous une forme interprétable par EFICAS
150 return "COMMENTAIRE(u"+t+")\n"
152 #s='COMMENTAIRE(u"""'+self.texte+'""")\n\n'
155 def append_text(self,texte):
157 Ajoute texte a self.texte en enlevant le # initial
161 self.texte = self.texte+texte[1:]
163 # le dièse n'est pas sur le premier caractere
164 amont,aval = string.split(texte,'#',1) # on découpe suivant la première occurrence de #
165 self.texte = self.texte +amont + aval
167 class COMMANDE(ENTITE_JDC):
173 return self.texte+'\n'
175 def get_nb_par(self):
177 Retourne la différence entre le nombre de parenthèses ouvrantes
178 et le nombre de parenthèses fermantes présentes dans self.texte
179 Peut donc retourner un entier négatif
181 # faire attention aux commentaires contenus dans self.texte
182 # qui peuvent eux-memes contenir des parenthèses !!!!
183 l_lignes = string.split(self.texte,'\n')
185 for ligne in l_lignes:
186 ligne = string.split(ligne,'#')[0]
187 nb = nb + (string.count(ligne,'(')-string.count(ligne,')'))
190 class AFFECTATION(ENTITE_JDC):
192 def append_text(self,texte):
194 Ajoute texte a self.texte en enlevant tout retour chariot et tout point virgule
195 PN et tout commentaire
197 if texte[-1] == '\n' : texte = string.rstrip(texte[0:-1])
198 if texte[-1] == ';' : texte = string.rstrip(texte[0:-1])
199 self.texte = self.texte+texte+'\n'
203 Retourne une expression de l'affectation compréhensible par ACCAS
204 et exploitable par EFICAS
206 nom,valeur = string.split(self.texte,'=',1)
207 n = string.rstrip(nom)
208 nom = string.lstrip(n)
209 if valeur[-1] == '\n': valeur = valeur[:-1]
210 return n + ' = PARAMETRE(nom=\''+nom+'\',valeur='+valeur+')\n'
212 class COMMANDE_COMMENTARISEE(ENTITE_JDC):
214 def append_text(self,texte):
216 Ajoute texte a self.texte en enlevant les doubles commentaires
218 texte = string.strip(texte)
219 texte = string.strip(texte[2:])
220 self.texte = self.texte+(len(self.texte)>0)*'\n'+texte
224 Retourne une expression de la commande commentarisée compréhensible par ACCAS
225 et exploitable par EFICAS
227 return "COMMANDE_COMM(texte="+repr(self.texte)+")\n"
228 #return "COMMANDE_COMM(texte='''"+self.texte+"''')\n"
230 class AFFECTATION_EVAL(ENTITE_JDC):
232 def append_text(self,texte):
234 Ajoute texte a self.texte en enlevant tout retour chariot
236 if texte[-1] == '\n' : texte = texte[1:-1]
237 self.texte = self.texte+texte
241 Retourne une expression du paramètre EVAL compréhensible par ACCAS
242 et exploitable par EFICAS
244 nom,valeur = string.split(self.texte,'=',1)
245 nom = string.strip(nom)
246 if valeur[-1] == '\n': valeur = valeur[:-1]
247 valeur = string.strip(valeur)
248 return nom+' = PARAMETRE_EVAL(nom=\''+nom+'\',valeur=\''+valeur+'\')\n\n'
250 class PARSEUR_PYTHON:
252 Cette classe sert a générer un objet PARSEUR_PYTHON qui réalise l'analyse d'un texte
253 représentant un JDC Python en distinguant :
254 - les commentaires inter commandes
258 pattern_commande = re.compile(r'^([A-Z][A-Z0-9_]+)([ \t\r\f\v]*)\(([\w\W]*)')
259 pattern_eval = re.compile(r'^(EVAL)([ \t\r\f\v]*)\(([\w\W]*)')
260 pattern_ligne_vide = re.compile(r'^[\t\r\f\v\n]+')
261 pattern_name = re.compile(r'[a-zA-Z_]\w*')
263 def __init__(self,texte):
268 def is_affectation(self,texte):
270 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation dans un jeu de commandes
273 if '=' not in texte : return 0
274 if self.pattern_commande.match(texte):
275 # cas d'une procédure ...
277 amont,aval = string.split(texte,'=',1)
278 aval = string.strip(aval)
279 if self.pattern_commande.match(aval):
282 s= string.strip(amont)
283 m= self.pattern_name.match(s)
284 if m is None : return 0
285 if m.start() != 0 :return 0
286 if m.end() != len(s):return 0
287 #print texte,amont,aval
290 def is_eval(self,texte):
292 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation de type EVAL
293 dans un jeu de commandes Aster, 0 sinon
295 if '=' not in texte : return 0
296 if self.pattern_commande.match(texte):
297 # cas d'une procédure ...
299 amont,aval = string.split(texte,'=',1)
300 aval = string.strip(aval)
301 if not self.pattern_commande.match(aval) : return 0
302 if self.pattern_eval.match(aval):
307 def is_commande(self,texte):
309 Méthode booléenne qui retourne 1 si le texte est celui d'une commande dans un jeu de commandes
312 if self.pattern_commande.match(texte):
313 # cas d'une procédure ...
315 # A ce stade il faut avoir un OPER ou une MACRO, bref un '=' !
316 if '=' not in texte : return 0
317 # on a un texte de la forme xxxx = yyyyy
318 # --> reste a analyser yyyy
319 amont,aval = string.split(texte,'=',1)
320 aval = string.strip(aval)
321 if self.pattern_commande.match(aval):
328 Eclate la chaine self.texte en self.l_objets une liste lignes d'instructions
329 et de commentaires (parmi lesquels des instructions "commentarisées").
331 l_lignes = string.split(self.texte,'\n')
332 commentaire_courant = None
333 commande_courante = None
334 affectation_courante = None
335 commande_commentarisee_courante = None
338 #initialisation du nombre de parentheses non fermees et de commentaires non termines
339 #Attention a reinitialiser en fin de ligne logique
340 #Une ligne logique peut s'etendre sur plusieurs lignes physiques avec des caracteres de continuation
341 #explicites ou implicites
342 hangingBraces = list(emptyHangingBraces)
345 #Masquage des commentaires et strings multilignes
346 srcMasked=maskStringsAndComments('\n'.join(l_lignes))
348 masked_lines=srcMasked.split('\n')
351 for ligne in l_lignes :
352 line=masked_lines[lineno]
355 # mise a jour du nombre total de parentheses ouvertes (non fermees)
356 # et du nombre de commentaires non termines
357 for i in range(len(implicitContinuationChars)):
358 contchar = implicitContinuationChars[i]
359 numHanging = hangingBraces[i]
360 hangingBraces[i] = numHanging+line.count(contchar[0]) - line.count(contchar[1])
362 hangingComments ^= line.count('"""') % 2
363 hangingComments ^= line.count(u"'''") % 2
364 #print hangingComments,hangingBraces
365 if hangingBraces[0] < 0 or hangingBraces[1] < 0 or hangingBraces[2] < 0:
366 raise ParserException()
368 if string.strip(ligne) == '':
369 # il s'agit d'un saut de ligne
373 if pattern_2comments.match(ligne):
374 #on a trouvé une commande commentarisée : double commentaire sans rien devant a part des blancs
375 if commentaire_courant:
376 #Si un commentaire ordinaire est en cours on le termine
377 commentaire_courant = None
379 if commande_courante :
380 # on a un objet commentarisé a l'intérieur d'une commande
381 # --> non traité pour l'instant : on l'ajoute simplement a la commande courante comme
382 # un commentaire ordinaire
383 commande_courante.append_text(ligne)
384 elif commande_commentarisee_courante :
385 # commande_commentarisee en cours : on ajoute la ligne
386 commande_commentarisee_courante.append_text(ligne)
387 # on a 2 commandes commentarisées de suite
388 if pattern_finComments.match(ligne) :
389 commande_commentarisee_courante = None
391 # debut de commande commentarisée : on crée un objet commande_commentarisee_courante
392 commande_commentarisee_courante = COMMANDE_COMMENTARISEE(self)
393 commande_commentarisee_courante.append_text(ligne)
395 #on passe a la ligne suivante
398 if pattern_comment.match(ligne):
399 #commentaire ordinaire avec seulement des blancs devant
400 if commande_commentarisee_courante :
401 # commande_commentarisee en cours : on la clot
402 commande_commentarisee_courante = None
404 if commande_courante :
405 # il s'agit d'un commentaire a l'intérieur d'une commande --> on ne fait rien de special
406 #on l'ajoute au texte de la commande
407 commande_courante.append_text(ligne)
408 elif commentaire_courant :
409 # il s'agit de la nième ligne d'un commentaire entre deux commandes
410 # --> on ajoute cette ligne au commentaire courant
411 commentaire_courant.append_text(ligne)
413 # il s'agit d'un nouveau commentaire entre deux commandes
414 # --> on le crée et il devient le commentaire courant
415 commentaire_courant = COMMENTAIRE(self)
416 commentaire_courant.append_text(ligne)
418 #on passe a la ligne suivante
421 # la ligne contient des données autre qu'un éventuel commentaire
422 if commentaire_courant :
423 # on clot un éventuel commentaire courant
424 commentaire_courant = None
426 if commande_commentarisee_courante :
427 # on clot une éventuelle commande commentarisee courante
428 commande_commentarisee_courante = None
430 if commande_courante :
431 #on a une commande en cours. On l'enrichit ou on la termine
432 commande_courante.append_text(ligne)
433 if not linecontinueRE.search(line) \
434 and (hangingBraces == emptyHangingBraces) \
435 and not hangingComments:
436 #la commande est terminée
437 #print "fin de commande"
438 self.analyse_reel(commande_courante.texte)
439 commande_courante = None
441 #on passe a la ligne suivante
444 if affectation_courante != None :
445 #poursuite d'une affectation
446 affectation_courante.append_text(ligne)
447 if not linecontinueRE.search(line) \
448 and (hangingBraces == emptyHangingBraces) \
449 and not hangingComments:
450 #L'affectation est terminée
451 affectation_courante=None
452 #on passe a la ligne suivante
455 # il peut s'agir d'une commande ou d'une affectation ...
457 if self.is_eval(ligne):
458 # --> affectation de type EVAL
459 if affectation_courante : affectation_courante = None
460 affectation = AFFECTATION_EVAL(self)
461 affectation.append_text(ligne)
462 #on passe a la ligne suivante
465 if self.is_affectation(ligne):
468 #traitement des commentaires en fin de ligne
469 compos=line.find(u"#")
471 #commentaire en fin de ligne
472 #on cree un nouveau commentaire avant le parametre
473 COMMENTAIRE(self).append_text(ligne[compos:])
475 #si plusieurs instructions separees par des ; sur la meme ligne
476 inspos=line.find(u";")
478 #on garde seulement la premiere partie de la ligne
479 #si on a que des blancs apres le point virgule
480 if string.strip(text[inspos:]) == ";":
483 raise FatalError(tr("Eficas ne peut pas traiter plusieurs instructions \
484 sur la meme ligne : %s", ligne))
486 affectation_courante = AFFECTATION(self)
487 affectation_courante.append_text(text)
488 if not linecontinueRE.search(line) \
489 and (hangingBraces == emptyHangingBraces) \
490 and not hangingComments:
491 #L'affectation est terminée
492 affectation_courante=None
493 #on passe a la ligne suivante
496 if self.is_commande(ligne):
497 # --> nouvelle commande
498 affectation_courante = None
499 commande_courante = COMMANDE(self)
500 commande_courante.append_text(ligne)
501 #si la commande est complète, on la termine
502 if not linecontinueRE.search(line) \
503 and (hangingBraces == emptyHangingBraces) \
504 and not hangingComments:
505 #la commande est terminée
506 #print "fin de commande"
507 self.analyse_reel(commande_courante.texte)
508 commande_courante = None
509 #on passe a la ligne suivante
512 def enleve (self,texte) :
513 """Supprime de texte tous les caracteres blancs, fins de ligne, tabulations
514 Le nouveau texte est retourné
518 while (i<len(texte)):
519 if (texte[i] == " " or texte[i] == "\n" or texte[i] == "\t") :
522 chaine=chaine+texte[i]
526 def construit_genea(self,texte):
531 # traitement pour chaque caractere
532 while (indiceC < len(texte)):
534 if ( c == "," or c == "(u" or c == ")"):
537 #on doit trouver derriere soit une valeur soit une parenthese
539 nouvelindice=indiceC+1
540 if texte[nouvelindice] != "(u":
541 #pas de parenthese ouvrante derriere un signe =, on a une valeur.
542 while ( texte[nouvelindice] != "," and texte[nouvelindice] != ")"):
543 valeur=valeur+texte[nouvelindice]
544 nouvelindice=nouvelindice+1
545 if nouvelindice == len(texte) :
546 nouvelindice=nouvelindice -1
548 if mot in self.appli.liste_simp_reel:
552 if str(clef) != str(valeur) :
553 dict_reel_concept[clef]=valeur
559 #parenthese ouvrante derriere un signe =, on a un tuple de valeur ou de mots cles facteurs.
560 # s agit -il d un tuple
561 if texte[nouvelindice+1] != "(u":
562 #le suivant n'est pas une parenthese ouvrante : on a un tuple de valeurs ou un mot cle facteur
564 #on avance jusqu'a la fin du tuple de valeurs ou jusqu'a la fin du premier mot cle simple
565 #contenu dans le mot cle facteur
566 while ( texte[nouvelindice] != "="):
567 if texte[nouvelindice] == ")" :
571 nouvelindice=nouvelindice+1
572 if nouvelindice == len(texte) :
573 nouvelindice=nouvelindice -1
576 #cas du tuple de valeurs
577 valeur=texte[indiceC+1:nouvelindice+1]
578 indiceC=nouvelindice+1
579 if mot in self.appli.liste_simp_reel:
581 for val in valeur.split(',') :
582 # Attention la derniere valeur est""
586 if str(clef) != str(val) :
587 dict_reel_concept[clef]=val
593 #cas du mocle facteur simple ou
596 mot=mot+texte[indiceC]
598 # traitement du dernier inutile
600 return dict_reel_concept
602 def analyse_reel(self,commande) :
604 # On verifie qu on a bien un OPER
606 if commande.find(u"=") > commande.find(u"(u") :
608 if commande.find(u"=") > 0:
609 #epure1=self.enleve(commande)
610 epure1=pattern_blancs.sub(u"",commande)
611 nomConcept,corps=epure1.split(u"=",1)
612 epure2=corps.replace(u"_F(u","(u")
613 #nomConcept=epure1.split(u"=")[0]
614 #index=epure1.find(u"=")
615 #epure2=epure1[index+1:len(epure1)].replace(u"_F(u","(u")
616 #dict_reel_concept=self.construit_genea(epure2)
618 dict_reel_concept=construit_genea(epure2,self.appli.liste_simp_reel)
621 if nomConcept == "sansnom" :
623 if nomConcept !=None :
624 if len(dict_reel_concept) != 0:
625 self.appli.dict_reels[nomConcept]=dict_reel_concept
627 def get_texte(self,appli=None):
629 Retourne le texte issu de l'analyse
634 if not self.l_objets : self.analyse()
636 for obj in self.l_objets:
639 except ParserException:
640 #Impossible de convertir le texte, on le retourne tel que
645 import parseur_python
647 doctest.testmod(parseur_python)
650 if __name__ == "__main__" :
652 #fichier = 'D:/Eficas_dev/Tests/zzzz100a.comm'
653 fichier = 'U:/Eficas_dev/Tests/test_eval.comm'
654 fichier = '/local/chris/ASTER/Eficas/Eficas1_10/EficasV1/Tests/testcomm/b.comm'
655 fichier = '/local/chris/ASTER/instals/STA8.2/astest/forma12c.comm'
656 fichier = 'titi.comm'
657 fichier = '../Aster/sdls300a.comm'
658 fichier = '../Aster/az.comm'
659 texte = open(fichier,'r').read()
662 liste_simp_reel=["VALE","VALE_C","GROUP_MA","RAYON"]
667 txt = PARSEUR_PYTHON(texte).get_texte(a)
668 print t0,time.clock()-t0
670 import hotshot, hotshot.stats
671 prof = hotshot.Profile(u"stones.prof")
672 txt = prof.runcall(PARSEUR_PYTHON(texte).get_texte,a)
674 stats = hotshot.stats.load(u"stones.prof")
676 stats.sort_stats('time', 'calls')
677 stats.print_stats(20)
680 compile(txt, '<string>', 'exec')