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
20 from __future__ import absolute_import
21 from __future__ import print_function
23 from builtins import str
24 from builtins import range
25 from builtins import object
30 from Extensions.i18n import tr
31 from six.moves import range
33 escapedQuotesRE = re.compile(r"(\\\\|\\\"|\\\')")
34 stringsAndCommentsRE = \
35 re.compile(u"(\"\"\".*?\"\"\"|'''.*?'''|\"[^\"]*\"|\'[^\']*\'|#.*?\n)", re.DOTALL)
36 #stringsAndCommentsRE = \
37 # re.compile(u"(\"\"\".*\"\"\"|'''.*'''|\"[^\"]*\"|\'[^\']*\'|#.*\n)", re.DOTALL)
40 allchars = string.maketrans(u"", "")
41 allcharsExceptNewline = allchars[: allchars.index('\n')]+allchars[allchars.index('\n')+1:]
42 allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
44 allchars=bytes.maketrans(b"",b"")
45 allcharsExceptNewline = allchars[: allchars.index(b'\n')]+allchars[allchars.index(b'\n')+1:]
46 allcharsExceptNewlineTranstable = bytes.maketrans(allcharsExceptNewline, b'*'*len(allcharsExceptNewline))
48 #if sys.platform[0:5]=="linux" :
49 # allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
50 #elif sys.platform[0:3]=="win" :
51 # allcharsExceptNewlineTranstable = dict((ord(char), u'*') for char in allcharsExceptNewline)#
53 # allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline))
55 def maskStringsAndComments(src):
56 """Masque tous les caracteres de src contenus dans des commentaires ou des strings multilignes (triples
58 Le masquage est realise en remplacant les caracteres par des *
59 Attention : cette fonction doit etre utilisee sur un texte complet et pas ligne par ligne
61 # remplace les \\, les \" les \' par **
62 # supprime toutes les chaines ou commentaires ,y compris multiligne i
63 # entre 3 ou 1 simples ou doubles quotes (ouvrantes fermantes) ou #
64 # laisse les non fermantes ou non ouvrantes
65 # on prend 1 sur 2 en raison du split qui donne python, commentaire, python, commentaire...
67 src = escapedQuotesRE.sub("**", src)
68 allstrings = stringsAndCommentsRE.split(src)
70 # on a une liste d elements constituee successivement de (string, comment)
71 for i in range(1, len(allstrings), 2):
72 if allstrings[i].startswith(u"'''")or allstrings[i].startswith('"""'):
73 allstrings[i] = allstrings[i][:3]+ \
74 allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \
77 allstrings[i] = allstrings[i][0]+ \
78 allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \
81 return "".join(allstrings)
83 implicitContinuationChars = (('(', ')'), ('[', ']'), ('{', '}'))
84 linecontinueRE = re.compile(r"\\\s*(#.*)?$")
85 emptyHangingBraces = [0,0,0,0,0]
87 class ParserException(Exception): pass
88 class FatalError(Exception): pass
90 #commentaire double precede d'un nombre quelconque de blancs (pas multiligne)
91 pattern_2comments = re.compile(r"^\s*##.*")
92 pattern_finComments = re.compile("^\s*##Fin Commentaire")
93 #commentaire standard precede d'un nombre quelconque de blancs (pas multiligne)
94 pattern_comment = re.compile(r"^\s*#.*")
95 #fin de ligne ; suivi d'un nombre quelconque de blancs (pas multiligne)
96 pattern_fin = re.compile(r"; *$")
97 #pattern pour supprimer les blancs, tabulations et fins de ligne
98 pattern_blancs = re.compile(r"[ \t\r\f\v]")
99 #pattern_blancs = re.compile(r"[\s\n]")
100 number_kw_pattern=re.compile(r"""
102 #groupe nombre decimal
104 #signe : on ignore le signe +
106 #groupe (avec ?: n'apparait pas en tant que groupe dans le resultat)
108 #mantisse forme entiere.fractionnaire
111 #ou forme .fractionnaire
120 """,re.VERBOSE|re.MULTILINE)
122 def construit_genea(texte,liste_mc):
124 Retourne un dictionnaire dont les cles sont des reels et les valeurs sont leurs representations textuelles.
126 Realise un filtrage sur les reels :
128 - Ne garde que les reels pour lesquels str ne donne pas une bonne representation.
129 - Ne garde que les reels derriere un argument keyword dont le nom est dans liste_mc
131 >>> 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),
132 ... #POMPE_PRIMA._BOUCLE_N._2_ELEMENT_NUMERO:0239
133 ... h=_F(x=34.6,y=-1)'''
134 >>> construit_genea(s,['a','x'])
135 {0.000213: '21.3e-5'}
139 #on masque les strings et commentaires pour ne pas identifier de faux reels
140 for m in number_kw_pattern.findall(maskStringsAndComments(texte)):
145 if mot not in liste_mc:continue
148 if str(key) != m: d[key]=m
152 class ENTITE_JDC(object) :
153 """Classe de base pour tous les objets crees lors de la conversion
154 Tout objet derive est enregistre aupres de son pere a sa creation
156 def __init__(self,pere):
158 pere.l_objets.append(self)
160 def set_text(self,texte):
163 def append_text(self,texte):
165 Ajoute texte a self.texte en mettant un retour chariot a la fin de texte
168 self.texte = self.texte +texte
173 class COMMENTAIRE(ENTITE_JDC):
177 Retourne une chaine de caracteres representants self
178 sous une forme interpretable par EFICAS
181 return "COMMENTAIRE(u"+t+")\n"
183 #s='COMMENTAIRE(u"""'+self.texte+'""")\n\n'
186 def append_text(self,texte):
188 Ajoute texte a self.texte en enlevant le # initial
192 self.texte = self.texte+texte[1:]
194 # le diese n'est pas sur le premier caractere
195 amont,aval = texte.split('#',1) # on decoupe suivant la premiere occurrence de #
196 self.texte = self.texte +amont + aval
198 class COMMANDE(ENTITE_JDC):
204 return self.texte+'\n'
206 def get_nb_par(self):
208 Retourne la difference entre le nombre de parentheses ouvrantes
209 et le nombre de parentheses fermantes presentes dans self.texte
210 Peut donc retourner un entier negatif
212 # faire attention aux commentaires contenus dans self.texte
213 # qui peuvent eux-memes contenir des parentheses !!!!
214 l_lignes = self.texte.split('\n')
216 for ligne in l_lignes:
217 ligne = ligne.split('#')[0]
218 #nb = nb + (string.count(ligne,'(')-string.count(ligne,')'))
220 nb = nb + ( ligne.count('(') - ligne.count(')') )
223 class AFFECTATION(ENTITE_JDC):
225 def append_text(self,texte):
227 Ajoute texte a self.texte en enlevant tout retour chariot et tout point virgule
228 PN et tout commentaire
230 if texte[-1] == '\n' : texte = texte[0:-1].rstrip()
231 if texte[-1] == ';' : texte = texte[0:-1].rstrip()
232 self.texte = self.texte+texte+'\n'
236 Retourne une expression de l'affectation comprehensible par ACCAS
237 et exploitable par EFICAS
239 nom,valeur = self.texte.split('=',1)
242 if valeur[-1] == '\n': valeur = valeur[:-1]
243 return n + ' = PARAMETRE(nom=\''+nom+'\',valeur='+valeur+')\n'
245 class COMMANDE_COMMENTARISEE(ENTITE_JDC):
247 def append_text(self,texte):
249 Ajoute texte a self.texte en enlevant les doubles commentaires
251 texte = texte.strip()
252 texte = texte[2:].strip()
253 self.texte = self.texte+(len(self.texte)>0)*'\n'+texte
257 Retourne une expression de la commande commentarisee comprehensible par ACCAS
258 et exploitable par EFICAS
260 return "COMMANDE_COMM(texte="+repr(self.texte)+")\n"
261 #return "COMMANDE_COMM(texte='''"+self.texte+"''')\n"
263 class AFFECTATION_EVAL(ENTITE_JDC):
265 def append_text(self,texte):
267 Ajoute texte a self.texte en enlevant tout retour chariot
269 if texte[-1] == '\n' : texte = texte[1:-1]
270 self.texte = self.texte+texte
274 Retourne une expression du parametre EVAL comprehensible par ACCAS
275 et exploitable par EFICAS
277 nom,valeur = self.texte.split('=',1)
279 if valeur[-1] == '\n': valeur = valeur[:-1]
280 valeur = valeur.strip()
281 return nom+' = PARAMETRE_EVAL(nom=\''+nom+'\',valeur=\''+valeur+'\')\n\n'
283 class PARSEUR_PYTHON(object):
285 Cette classe sert a generer un objet PARSEUR_PYTHON qui realise l'analyse d'un texte
286 representant un JDC Python en distinguant :
287 - les commentaires inter commandes
291 pattern_commande = re.compile(r'^([A-Z][a-zA-Z0-9_]+)([ \t\r\f\v]*)\(([\w\W]*)')
292 pattern_eval = re.compile(r'^(EVAL)([ \t\r\f\v]*)\(([\w\W]*)')
293 pattern_ligne_vide = re.compile(r'^[\t\r\f\v\n]+')
294 pattern_name = re.compile(r'[a-zA-Z_]\w*')
296 def __init__(self,texte):
301 def is_affectation(self,texte):
303 Methode booleenne qui retourne 1 si le texte est celui d'une affectation dans un jeu de commandes
306 if '=' not in texte : return 0
307 if self.pattern_commande.match(texte):
308 # cas d'une procedure ...
310 amont,aval = texte.split('=',1)
314 if self.pattern_commande.match(aval):
318 m= self.pattern_name.match(s)
319 if m is None : return 0
320 if m.start() != 0 :return 0
321 if m.end() != len(s):return 0
324 def is_eval(self,texte):
326 Methode booleenne qui retourne 1 si le texte est celui d'une affectation de type EVAL
327 dans un jeu de commandes Aster, 0 sinon
329 if '=' not in texte : return 0
330 if self.pattern_commande.match(texte):
331 # cas d'une procedure ...
333 amont,aval = texte.split('=',1)
335 if not self.pattern_commande.match(aval) : return 0
336 if self.pattern_eval.match(aval):
341 def is_commande(self,texte):
343 Methode booleenne qui retourne 1 si le texte est celui d'une commande dans un jeu de commandes
346 if self.pattern_commande.match(texte):
347 # cas d'une procedure ...
349 # A ce stade il faut avoir un OPER ou une MACRO, bref un '=' !
350 if '=' not in texte : return 0
351 # on a un texte de la forme xxxx = yyyyy
352 # --> reste a analyser yyyy
353 amont,aval = texte.split('=',1)
355 if self.pattern_commande.match(aval):
360 def is_modification_catalogue(self,texte) :
361 if self.pattern_commande.match(texte):
366 Eclate la chaine self.texte en self.l_objets une liste lignes d'instructions
367 et de commentaires (parmi lesquels des instructions "commentarisees").
369 l_lignes = self.texte.split('\n')
370 commentaire_courant = None
371 commande_courante = None
372 affectation_courante = None
373 commande_commentarisee_courante = None
376 #initialisation du nombre de parentheses non fermees et de commentaires non termines
377 #Attention a reinitialiser en fin de ligne logique
378 #Une ligne logique peut s'etendre sur plusieurs lignes physiques avec des caracteres de continuation
379 #explicites ou implicites
380 hangingBraces = list(emptyHangingBraces)
383 #Masquage des commentaires et strings multilignes
384 srcMasked=maskStringsAndComments('\n'.join(l_lignes))
385 masked_lines=srcMasked.split('\n')
388 for ligne in l_lignes :
389 line=masked_lines[lineno]
391 #print ("ligne:",line)
392 # mise a jour du nombre total de parentheses ouvertes (non fermees)
393 # et du nombre de commentaires non termines
394 for i in range(len(implicitContinuationChars)):
395 contchar = implicitContinuationChars[i]
396 numHanging = hangingBraces[i]
397 hangingBraces[i] = numHanging+line.count(contchar[0]) - line.count(contchar[1])
399 hangingComments ^= line.count('"""') % 2
400 hangingComments ^= line.count(u"'''") % 2
401 #print (hangingComments,hangingBraces)
402 if hangingBraces[0] < 0 or hangingBraces[1] < 0 or hangingBraces[2] < 0:
403 raise ParserException()
405 if ligne.strip() == '':
406 # il s'agit d'un saut de ligne
410 if pattern_2comments.match(ligne):
411 #on a trouve une commande commentarisee : double commentaire sans rien devant a part des blancs
412 if commentaire_courant:
413 #Si un commentaire ordinaire est en cours on le termine
414 commentaire_courant = None
416 if commande_courante :
417 # on a un objet commentarise a l'interieur d'une commande
418 # --> non traite pour l'instant : on l'ajoute simplement a la commande courante comme
419 # un commentaire ordinaire
420 commande_courante.append_text(ligne)
421 elif commande_commentarisee_courante :
422 # commande_commentarisee en cours : on ajoute la ligne
423 commande_commentarisee_courante.append_text(ligne)
424 # on a 2 commandes commentarisees de suite
425 if pattern_finComments.match(ligne) :
426 commande_commentarisee_courante = None
428 # debut de commande commentarisee : on cree un objet commande_commentarisee_courante
429 commande_commentarisee_courante = COMMANDE_COMMENTARISEE(self)
430 commande_commentarisee_courante.append_text(ligne)
432 #on passe a la ligne suivante
435 if pattern_comment.match(ligne):
436 #commentaire ordinaire avec seulement des blancs devant
437 if commande_commentarisee_courante :
438 # commande_commentarisee en cours : on la clot
439 commande_commentarisee_courante = None
441 if commande_courante :
442 # il s'agit d'un commentaire a l'interieur d'une commande --> on ne fait rien de special
443 #on l'ajoute au texte de la commande
444 commande_courante.append_text(ligne)
445 elif commentaire_courant :
446 # il s'agit de la nieme ligne d'un commentaire entre deux commandes
447 # --> on ajoute cette ligne au commentaire courant
448 commentaire_courant.append_text(ligne)
450 # il s'agit d'un nouveau commentaire entre deux commandes
451 # --> on le cree et il devient le commentaire courant
452 commentaire_courant = COMMENTAIRE(self)
453 commentaire_courant.append_text(ligne)
455 #on passe a la ligne suivante
458 # la ligne contient des donnees autre qu'un eventuel commentaire
459 if commentaire_courant :
460 # on clot un eventuel commentaire courant
461 commentaire_courant = None
463 if commande_commentarisee_courante :
464 # on clot une eventuelle commande commentarisee courante
465 commande_commentarisee_courante = None
467 if commande_courante :
468 #on a une commande en cours. On l'enrichit ou on la termine
469 commande_courante.append_text(ligne)
470 if not linecontinueRE.search(line) \
471 and (hangingBraces == emptyHangingBraces) \
472 and not hangingComments:
473 #la commande est terminee
474 self.analyse_reel(commande_courante.texte)
475 commande_courante = None
477 #on passe a la ligne suivante
480 if affectation_courante != None :
481 #poursuite d'une affectation
482 affectation_courante.append_text(ligne)
483 if not linecontinueRE.search(line) \
484 and (hangingBraces == emptyHangingBraces) \
485 and not hangingComments:
486 #L'affectation est terminee
487 affectation_courante=None
488 #on passe a la ligne suivante
491 # il peut s'agir d'une commande ou d'une affectation ...
493 if self.is_eval(ligne):
494 # --> affectation de type EVAL
495 if affectation_courante : affectation_courante = None
496 affectation = AFFECTATION_EVAL(self)
497 affectation.append_text(ligne)
498 #on passe a la ligne suivante
501 if self.is_affectation(ligne):
502 #print( '--> affectation')
504 #traitement des commentaires en fin de ligne
505 compos=line.find(u"#")
507 #commentaire en fin de ligne
508 #on cree un nouveau commentaire avant le parametre
509 COMMENTAIRE(self).append_text(ligne[compos:])
511 #si plusieurs instructions separees par des ; sur la meme ligne
512 inspos=line.find(u";")
514 #on garde seulement la premiere partie de la ligne
515 #si on a que des blancs apres le point virgule
516 if text[inspos:].strip() == ";":
519 raise FatalError(tr("Eficas ne peut pas traiter plusieurs instructions \
520 sur la meme ligne : %s", ligne))
522 affectation_courante = AFFECTATION(self)
523 affectation_courante.append_text(text)
524 if not linecontinueRE.search(line) \
525 and (hangingBraces == emptyHangingBraces) \
526 and not hangingComments:
527 #L'affectation est terminee
528 affectation_courante=None
529 #on passe a la ligne suivante
532 if self.is_commande(ligne):
533 # --> nouvelle commande
534 affectation_courante = None
535 commande_courante = COMMANDE(self)
536 commande_courante.append_text(ligne)
537 #si la commande est complete, on la termine
538 if not linecontinueRE.search(line) \
539 and (hangingBraces == emptyHangingBraces) \
540 and not hangingComments:
541 #la commande est terminee
542 self.analyse_reel(commande_courante.texte)
543 commande_courante = None
544 #on passe a la ligne suivante
548 def enleve (self,texte) :
549 """Supprime de texte tous les caracteres blancs, fins de ligne, tabulations
550 Le nouveau texte est retourne
554 while (i<len(texte)):
555 if (texte[i] == " " or texte[i] == "\n" or texte[i] == "\t") :
558 chaine=chaine+texte[i]
562 def construit_genea(self,texte):
567 # traitement pour chaque caractere
568 while (indiceC < len(texte)):
570 if ( c == "," or c == "(u" or c == ")"):
573 #on doit trouver derriere soit une valeur soit une parenthese
575 nouvelindice=indiceC+1
576 if texte[nouvelindice] != "(u":
577 #pas de parenthese ouvrante derriere un signe =, on a une valeur.
578 while ( texte[nouvelindice] != "," and texte[nouvelindice] != ")"):
579 valeur=valeur+texte[nouvelindice]
580 nouvelindice=nouvelindice+1
581 if nouvelindice == len(texte) :
582 nouvelindice=nouvelindice -1
584 if mot in self.appli.liste_simp_reel:
588 if str(clef) != str(valeur) :
589 dict_reel_concept[clef]=valeur
595 #parenthese ouvrante derriere un signe =, on a un tuple de valeur ou de mots cles facteurs.
596 # s agit -il d un tuple
597 if texte[nouvelindice+1] != "(u":
598 #le suivant n'est pas une parenthese ouvrante : on a un tuple de valeurs ou un mot cle facteur
600 #on avance jusqu'a la fin du tuple de valeurs ou jusqu'a la fin du premier mot cle simple
601 #contenu dans le mot cle facteur
602 while ( texte[nouvelindice] != "="):
603 if texte[nouvelindice] == ")" :
607 nouvelindice=nouvelindice+1
608 if nouvelindice == len(texte) :
609 nouvelindice=nouvelindice -1
612 #cas du tuple de valeurs
613 valeur=texte[indiceC+1:nouvelindice+1]
614 indiceC=nouvelindice+1
615 if mot in self.appli.liste_simp_reel:
617 for val in valeur.split(',') :
618 # Attention la derniere valeur est""
622 if str(clef) != str(val) :
623 dict_reel_concept[clef]=val
629 #cas du mocle facteur simple ou
632 mot=mot+texte[indiceC]
634 # traitement du dernier inutile
636 return dict_reel_concept
638 def analyse_reel(self,commande) :
640 # On verifie qu on a bien un OPER
642 if commande.find(u"=") > commande.find(u"(u") :
644 if commande.find(u"=") > 0:
645 #epure1=self.enleve(commande)
646 epure1=pattern_blancs.sub(u"",commande)
647 nomConcept,corps=epure1.split(u"=",1)
648 epure2=corps.replace(u"_F(u","(u")
649 #nomConcept=epure1.split(u"=")[0]
650 #index=epure1.find(u"=")
651 #epure2=epure1[index+1:len(epure1)].replace(u"_F(u","(u")
652 #dict_reel_concept=self.construit_genea(epure2)
654 dict_reel_concept=construit_genea(epure2,self.appli.liste_simp_reel)
657 if nomConcept == "sansnom" :
659 if nomConcept !=None :
660 if len(dict_reel_concept) != 0:
661 self.appli.dict_reels[nomConcept]=dict_reel_concept
663 def get_texte(self,appli=None):
665 Retourne le texte issu de l'analyse
670 if not self.l_objets : self.analyse()
672 for obj in self.l_objets:
675 except ParserException:
676 #Impossible de convertir le texte, on le retourne tel que
681 #import parseur_python
683 doctest.testmod(parseur_python)
686 if __name__ == "__main__" :
688 #fichier = 'D:/Eficas_dev/Tests/zzzz100a.comm'
689 #fichier = 'U:/Eficas_dev/Tests/test_eval.comm'
690 texte = open(fichier,'r').read()
693 liste_simp_reel=["VALE","VALE_C","GROUP_MA","RAYON"]
696 compile(txt, '<string>', 'exec')
697 print((a.dict_reels))