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 #stringsAndCommentsRE = \
28 # re.compile(u"(\"\"\".*\"\"\"|'''.*'''|\"[^\"]*\"|\'[^\']*\'|#.*\n)", re.DOTALL)
29 allchars = string.maketrans(u"", "")
30 allcharsExceptNewline = allchars[: allchars.index('\n')]+allchars[allchars.index('\n')+1:]
31 allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
33 #if sys.platform[0:5]=="linux" :
34 # allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
35 #elif sys.platform[0:3]=="win" :
36 # allcharsExceptNewlineTranstable = dict((ord(char), u'*') for char in allcharsExceptNewline)#
38 # allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
40 def maskStringsAndComments(src):
41 """Masque tous les caracteres de src contenus dans des commentaires ou des strings multilignes (triples
43 Le masquage est realise en remplacant les caracteres par des *
44 Attention : cette fonction doit etre utilisee sur un texte complet et pas ligne par ligne
46 # remplace les \\, les \" les \' par **
47 # supprime toutes les chaines ou commentaires ,y compris multiligne i
48 # entre 3 ou 1 simples ou doubles quotes (ouvrantes fermantes) ou #
49 # laisse les non fermantes ou non ouvrantes
50 #src = escapedQuotesRE.sub(u"**", src)
51 # le u met le bazar dans le translate
52 src = escapedQuotesRE.sub("**", src)
53 allstrings = stringsAndCommentsRE.split(src)
54 # every odd element is a string or comment
55 for i in xrange(1, len(allstrings), 2):
56 if allstrings[i].startswith(u"'''")or allstrings[i].startswith('"""'):
57 allstrings[i] = allstrings[i][:3]+ \
58 allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \
61 allstrings[i] = allstrings[i][0]+ \
62 allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \
65 return "".join(allstrings)
67 implicitContinuationChars = (('(', ')'), ('[', ']'), ('{', '}'))
68 linecontinueRE = re.compile(r"\\\s*(#.*)?$")
69 emptyHangingBraces = [0,0,0,0,0]
71 class ParserException(Exception): pass
72 class FatalError(Exception): pass
74 #commentaire double precede d'un nombre quelconque de blancs (pas multiligne)
75 pattern_2comments = re.compile(r"^\s*##.*")
76 pattern_finComments = re.compile("^\s*##Fin Commentaire")
77 #commentaire standard precede d'un nombre quelconque de blancs (pas multiligne)
78 pattern_comment = re.compile(r"^\s*#.*")
79 #fin de ligne ; suivi d'un nombre quelconque de blancs (pas multiligne)
80 pattern_fin = re.compile(r"; *$")
81 #pattern pour supprimer les blancs, tabulations et fins de ligne
82 pattern_blancs = re.compile(r"[ \t\r\f\v]")
83 #pattern_blancs = re.compile(r"[\s\n]")
84 number_kw_pattern=re.compile(r"""
86 #groupe nombre decimal
88 #signe : on ignore le signe +
90 #groupe (avec ?: n'apparait pas en tant que groupe dans le resultat)
92 #mantisse forme entiere.fractionnaire
95 #ou forme .fractionnaire
104 """,re.VERBOSE|re.MULTILINE)
106 def construit_genea(texte,liste_mc):
108 Retourne un dictionnaire dont les cles sont des reels et les valeurs sont leurs representations textuelles.
110 Realise un filtrage sur les reels :
112 - Ne garde que les reels pour lesquels str ne donne pas une bonne representation.
113 - Ne garde que les reels derriere un argument keyword dont le nom est dans liste_mc
115 >>> 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),
116 ... #POMPE_PRIMA._BOUCLE_N._2_ELEMENT_NUMERO:0239
117 ... h=_F(x=34.6,y=-1)'''
118 >>> construit_genea(s,['a','x'])
119 {0.000213: '21.3e-5'}
123 #on masque les strings et commentaires pour ne pas identifier de faux reels
124 for m in number_kw_pattern.findall(maskStringsAndComments(texte)):
129 if mot not in liste_mc:continue
132 if str(key) != m: d[key]=m
137 """Classe de base pour tous les objets créés lors de la conversion
138 Tout objet dérivé est enregistré aupres de son pere a sa création
140 def __init__(self,pere):
142 pere.l_objets.append(self)
144 def set_text(self,texte):
147 def append_text(self,texte):
149 Ajoute texte a self.texte en mettant un retour chariot a la fin de texte
152 self.texte = self.texte +texte
157 class COMMENTAIRE(ENTITE_JDC):
161 Retourne une chaine de caracteres représentants self
162 sous une forme interprétable par EFICAS
165 return "COMMENTAIRE(u"+t+")\n"
167 #s='COMMENTAIRE(u"""'+self.texte+'""")\n\n'
170 def append_text(self,texte):
172 Ajoute texte a self.texte en enlevant le # initial
176 self.texte = self.texte+texte[1:]
178 # le diese n'est pas sur le premier caractere
179 amont,aval = string.split(texte,'#',1) # on découpe suivant la premiere occurrence de #
180 self.texte = self.texte +amont + aval
182 class COMMANDE(ENTITE_JDC):
188 return self.texte+'\n'
190 def get_nb_par(self):
192 Retourne la différence entre le nombre de parentheses ouvrantes
193 et le nombre de parentheses fermantes présentes dans self.texte
194 Peut donc retourner un entier négatif
196 # faire attention aux commentaires contenus dans self.texte
197 # qui peuvent eux-memes contenir des parentheses !!!!
198 l_lignes = string.split(self.texte,'\n')
200 for ligne in l_lignes:
201 ligne = string.split(ligne,'#')[0]
202 nb = nb + (string.count(ligne,'(')-string.count(ligne,')'))
205 class AFFECTATION(ENTITE_JDC):
207 def append_text(self,texte):
209 Ajoute texte a self.texte en enlevant tout retour chariot et tout point virgule
210 PN et tout commentaire
212 if texte[-1] == '\n' : texte = string.rstrip(texte[0:-1])
213 if texte[-1] == ';' : texte = string.rstrip(texte[0:-1])
214 self.texte = self.texte+texte+'\n'
218 Retourne une expression de l'affectation compréhensible par ACCAS
219 et exploitable par EFICAS
221 nom,valeur = string.split(self.texte,'=',1)
222 n = string.rstrip(nom)
223 nom = string.lstrip(n)
224 if valeur[-1] == '\n': valeur = valeur[:-1]
225 return n + ' = PARAMETRE(nom=\''+nom+'\',valeur='+valeur+')\n'
227 class COMMANDE_COMMENTARISEE(ENTITE_JDC):
229 def append_text(self,texte):
231 Ajoute texte a self.texte en enlevant les doubles commentaires
233 texte = string.strip(texte)
234 texte = string.strip(texte[2:])
235 self.texte = self.texte+(len(self.texte)>0)*'\n'+texte
239 Retourne une expression de la commande commentarisée compréhensible par ACCAS
240 et exploitable par EFICAS
242 return "COMMANDE_COMM(texte="+repr(self.texte)+")\n"
243 #return "COMMANDE_COMM(texte='''"+self.texte+"''')\n"
245 class AFFECTATION_EVAL(ENTITE_JDC):
247 def append_text(self,texte):
249 Ajoute texte a self.texte en enlevant tout retour chariot
251 if texte[-1] == '\n' : texte = texte[1:-1]
252 self.texte = self.texte+texte
256 Retourne une expression du parametre EVAL compréhensible par ACCAS
257 et exploitable par EFICAS
259 nom,valeur = string.split(self.texte,'=',1)
260 nom = string.strip(nom)
261 if valeur[-1] == '\n': valeur = valeur[:-1]
262 valeur = string.strip(valeur)
263 return nom+' = PARAMETRE_EVAL(nom=\''+nom+'\',valeur=\''+valeur+'\')\n\n'
265 class PARSEUR_PYTHON:
267 Cette classe sert a générer un objet PARSEUR_PYTHON qui réalise l'analyse d'un texte
268 représentant un JDC Python en distinguant :
269 - les commentaires inter commandes
273 pattern_commande = re.compile(r'^([A-Z][A-Z0-9_]+)([ \t\r\f\v]*)\(([\w\W]*)')
274 pattern_eval = re.compile(r'^(EVAL)([ \t\r\f\v]*)\(([\w\W]*)')
275 pattern_ligne_vide = re.compile(r'^[\t\r\f\v\n]+')
276 pattern_name = re.compile(r'[a-zA-Z_]\w*')
278 def __init__(self,texte):
283 def is_affectation(self,texte):
285 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation dans un jeu de commandes
288 if '=' not in texte : return 0
289 if self.pattern_commande.match(texte):
290 # cas d'une procédure ...
292 amont,aval = string.split(texte,'=',1)
293 aval = string.strip(aval)
294 if self.pattern_commande.match(aval):
297 s= string.strip(amont)
298 m= self.pattern_name.match(s)
299 if m is None : return 0
300 if m.start() != 0 :return 0
301 if m.end() != len(s):return 0
304 def is_eval(self,texte):
306 Méthode booléenne qui retourne 1 si le texte est celui d'une affectation de type EVAL
307 dans un jeu de commandes Aster, 0 sinon
309 if '=' not in texte : return 0
310 if self.pattern_commande.match(texte):
311 # cas d'une procédure ...
313 amont,aval = string.split(texte,'=',1)
314 aval = string.strip(aval)
315 if not self.pattern_commande.match(aval) : return 0
316 if self.pattern_eval.match(aval):
321 def is_commande(self,texte):
323 Méthode booléenne qui retourne 1 si le texte est celui d'une commande dans un jeu de commandes
326 if self.pattern_commande.match(texte):
327 # cas d'une procédure ...
329 # A ce stade il faut avoir un OPER ou une MACRO, bref un '=' !
330 if '=' not in texte : return 0
331 # on a un texte de la forme xxxx = yyyyy
332 # --> reste a analyser yyyy
333 amont,aval = string.split(texte,'=',1)
334 aval = string.strip(aval)
335 if self.pattern_commande.match(aval):
340 def is_modification_catalogue(self,texte) :
341 if self.pattern_commande.match(texte):
346 Eclate la chaine self.texte en self.l_objets une liste lignes d'instructions
347 et de commentaires (parmi lesquels des instructions "commentarisées").
349 l_lignes = string.split(self.texte,'\n')
350 commentaire_courant = None
351 commande_courante = None
352 affectation_courante = None
353 commande_commentarisee_courante = None
356 #initialisation du nombre de parentheses non fermees et de commentaires non termines
357 #Attention a reinitialiser en fin de ligne logique
358 #Une ligne logique peut s'etendre sur plusieurs lignes physiques avec des caracteres de continuation
359 #explicites ou implicites
360 hangingBraces = list(emptyHangingBraces)
363 #Masquage des commentaires et strings multilignes
364 srcMasked=maskStringsAndComments('\n'.join(l_lignes))
365 masked_lines=srcMasked.split('\n')
368 for ligne in l_lignes :
369 line=masked_lines[lineno]
371 #print ("ligne:",line)
372 # mise a jour du nombre total de parentheses ouvertes (non fermees)
373 # et du nombre de commentaires non termines
374 for i in range(len(implicitContinuationChars)):
375 contchar = implicitContinuationChars[i]
376 numHanging = hangingBraces[i]
377 hangingBraces[i] = numHanging+line.count(contchar[0]) - line.count(contchar[1])
379 hangingComments ^= line.count('"""') % 2
380 hangingComments ^= line.count(u"'''") % 2
381 #print (hangingComments,hangingBraces)
382 if hangingBraces[0] < 0 or hangingBraces[1] < 0 or hangingBraces[2] < 0:
383 raise ParserException()
385 if string.strip(ligne) == '':
386 # il s'agit d'un saut de ligne
390 if pattern_2comments.match(ligne):
391 #on a trouvé une commande commentarisée : double commentaire sans rien devant a part des blancs
392 if commentaire_courant:
393 #Si un commentaire ordinaire est en cours on le termine
394 commentaire_courant = None
396 if commande_courante :
397 # on a un objet commentarisé a l'intérieur d'une commande
398 # --> non traité pour l'instant : on l'ajoute simplement a la commande courante comme
399 # un commentaire ordinaire
400 commande_courante.append_text(ligne)
401 elif commande_commentarisee_courante :
402 # commande_commentarisee en cours : on ajoute la ligne
403 commande_commentarisee_courante.append_text(ligne)
404 # on a 2 commandes commentarisées de suite
405 if pattern_finComments.match(ligne) :
406 commande_commentarisee_courante = None
408 # debut de commande commentarisée : on crée un objet commande_commentarisee_courante
409 commande_commentarisee_courante = COMMANDE_COMMENTARISEE(self)
410 commande_commentarisee_courante.append_text(ligne)
412 #on passe a la ligne suivante
415 if pattern_comment.match(ligne):
416 #commentaire ordinaire avec seulement des blancs devant
417 if commande_commentarisee_courante :
418 # commande_commentarisee en cours : on la clot
419 commande_commentarisee_courante = None
421 if commande_courante :
422 # il s'agit d'un commentaire a l'intérieur d'une commande --> on ne fait rien de special
423 #on l'ajoute au texte de la commande
424 commande_courante.append_text(ligne)
425 elif commentaire_courant :
426 # il s'agit de la nieme ligne d'un commentaire entre deux commandes
427 # --> on ajoute cette ligne au commentaire courant
428 commentaire_courant.append_text(ligne)
430 # il s'agit d'un nouveau commentaire entre deux commandes
431 # --> on le crée et il devient le commentaire courant
432 commentaire_courant = COMMENTAIRE(self)
433 commentaire_courant.append_text(ligne)
435 #on passe a la ligne suivante
438 # la ligne contient des données autre qu'un éventuel commentaire
439 if commentaire_courant :
440 # on clot un éventuel commentaire courant
441 commentaire_courant = None
443 if commande_commentarisee_courante :
444 # on clot une éventuelle commande commentarisee courante
445 commande_commentarisee_courante = None
447 if commande_courante :
448 #on a une commande en cours. On l'enrichit ou on la termine
449 commande_courante.append_text(ligne)
450 if not linecontinueRE.search(line) \
451 and (hangingBraces == emptyHangingBraces) \
452 and not hangingComments:
453 #la commande est terminée
454 self.analyse_reel(commande_courante.texte)
455 commande_courante = None
457 #on passe a la ligne suivante
460 if affectation_courante != None :
461 #poursuite d'une affectation
462 affectation_courante.append_text(ligne)
463 if not linecontinueRE.search(line) \
464 and (hangingBraces == emptyHangingBraces) \
465 and not hangingComments:
466 #L'affectation est terminée
467 affectation_courante=None
468 #on passe a la ligne suivante
471 # il peut s'agir d'une commande ou d'une affectation ...
473 if self.is_eval(ligne):
474 # --> affectation de type EVAL
475 if affectation_courante : affectation_courante = None
476 affectation = AFFECTATION_EVAL(self)
477 affectation.append_text(ligne)
478 #on passe a la ligne suivante
481 if self.is_affectation(ligne):
484 #traitement des commentaires en fin de ligne
485 compos=line.find(u"#")
487 #commentaire en fin de ligne
488 #on cree un nouveau commentaire avant le parametre
489 COMMENTAIRE(self).append_text(ligne[compos:])
491 #si plusieurs instructions separees par des ; sur la meme ligne
492 inspos=line.find(u";")
494 #on garde seulement la premiere partie de la ligne
495 #si on a que des blancs apres le point virgule
496 if string.strip(text[inspos:]) == ";":
499 raise FatalError(tr("Eficas ne peut pas traiter plusieurs instructions \
500 sur la meme ligne : %s", ligne))
502 affectation_courante = AFFECTATION(self)
503 affectation_courante.append_text(text)
504 if not linecontinueRE.search(line) \
505 and (hangingBraces == emptyHangingBraces) \
506 and not hangingComments:
507 #L'affectation est terminée
508 affectation_courante=None
509 #on passe a la ligne suivante
512 if self.is_commande(ligne):
513 # --> nouvelle commande
514 affectation_courante = None
515 commande_courante = COMMANDE(self)
516 commande_courante.append_text(ligne)
517 #si la commande est complete, on la termine
518 if not linecontinueRE.search(line) \
519 and (hangingBraces == emptyHangingBraces) \
520 and not hangingComments:
521 #la commande est terminee
522 self.analyse_reel(commande_courante.texte)
523 commande_courante = None
524 #on passe a la ligne suivante
528 def enleve (self,texte) :
529 """Supprime de texte tous les caracteres blancs, fins de ligne, tabulations
530 Le nouveau texte est retourne
534 while (i<len(texte)):
535 if (texte[i] == " " or texte[i] == "\n" or texte[i] == "\t") :
538 chaine=chaine+texte[i]
542 def construit_genea(self,texte):
547 # traitement pour chaque caractere
548 while (indiceC < len(texte)):
550 if ( c == "," or c == "(u" or c == ")"):
553 #on doit trouver derriere soit une valeur soit une parenthese
555 nouvelindice=indiceC+1
556 if texte[nouvelindice] != "(u":
557 #pas de parenthese ouvrante derriere un signe =, on a une valeur.
558 while ( texte[nouvelindice] != "," and texte[nouvelindice] != ")"):
559 valeur=valeur+texte[nouvelindice]
560 nouvelindice=nouvelindice+1
561 if nouvelindice == len(texte) :
562 nouvelindice=nouvelindice -1
564 if mot in self.appli.liste_simp_reel:
568 if str(clef) != str(valeur) :
569 dict_reel_concept[clef]=valeur
575 #parenthese ouvrante derriere un signe =, on a un tuple de valeur ou de mots cles facteurs.
576 # s agit -il d un tuple
577 if texte[nouvelindice+1] != "(u":
578 #le suivant n'est pas une parenthese ouvrante : on a un tuple de valeurs ou un mot cle facteur
580 #on avance jusqu'a la fin du tuple de valeurs ou jusqu'a la fin du premier mot cle simple
581 #contenu dans le mot cle facteur
582 while ( texte[nouvelindice] != "="):
583 if texte[nouvelindice] == ")" :
587 nouvelindice=nouvelindice+1
588 if nouvelindice == len(texte) :
589 nouvelindice=nouvelindice -1
592 #cas du tuple de valeurs
593 valeur=texte[indiceC+1:nouvelindice+1]
594 indiceC=nouvelindice+1
595 if mot in self.appli.liste_simp_reel:
597 for val in valeur.split(',') :
598 # Attention la derniere valeur est""
602 if str(clef) != str(val) :
603 dict_reel_concept[clef]=val
609 #cas du mocle facteur simple ou
612 mot=mot+texte[indiceC]
614 # traitement du dernier inutile
616 return dict_reel_concept
618 def analyse_reel(self,commande) :
620 # On verifie qu on a bien un OPER
622 if commande.find(u"=") > commande.find(u"(u") :
624 if commande.find(u"=") > 0:
625 #epure1=self.enleve(commande)
626 epure1=pattern_blancs.sub(u"",commande)
627 nomConcept,corps=epure1.split(u"=",1)
628 epure2=corps.replace(u"_F(u","(u")
629 #nomConcept=epure1.split(u"=")[0]
630 #index=epure1.find(u"=")
631 #epure2=epure1[index+1:len(epure1)].replace(u"_F(u","(u")
632 #dict_reel_concept=self.construit_genea(epure2)
634 dict_reel_concept=construit_genea(epure2,self.appli.liste_simp_reel)
637 if nomConcept == "sansnom" :
639 if nomConcept !=None :
640 if len(dict_reel_concept) != 0:
641 self.appli.dict_reels[nomConcept]=dict_reel_concept
643 def get_texte(self,appli=None):
645 Retourne le texte issu de l'analyse
650 if not self.l_objets : self.analyse()
652 for obj in self.l_objets:
655 except ParserException:
656 #Impossible de convertir le texte, on le retourne tel que
661 import parseur_python
663 doctest.testmod(parseur_python)
666 if __name__ == "__main__" :
668 #fichier = 'D:/Eficas_dev/Tests/zzzz100a.comm'
669 fichier = 'U:/Eficas_dev/Tests/test_eval.comm'
670 fichier = '/local/chris/ASTER/Eficas/Eficas1_10/EficasV1/Tests/testcomm/b.comm'
671 fichier = '/local/chris/ASTER/instals/STA8.2/astest/forma12c.comm'
672 fichier = 'titi.comm'
673 fichier = '../Aster/sdls300a.comm'
674 fichier = '../Aster/az.comm'
675 texte = open(fichier,'r').read()
678 liste_simp_reel=["VALE","VALE_C","GROUP_MA","RAYON"]
681 compile(txt, '<string>', 'exec')