PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
cosmetics.py
1"""
2This module implements the Cosmetics class containg methods to deal with cosmetics
3"""
4
5import re
6from pyfortool.util import debugDecor, nonCode, tag
7from pyfortool.expressions import createElem
8from pyfortool import NAMESPACE
9
10
11class Cosmetics():
12 """
13 Methods to deal with cosmetics
14 """
15 @debugDecor
16 def upperCase(self):
17 """
18 :return: same object but with upper case letters for FORTRAN code
19 """
20 for elem in self.iter():
21 if (not nonCode(elem)) and elem is not None and elem.text is not None:
22 elem.text = elem.text.upper()
23
24 @debugDecor
25 def lowerCase(self):
26 """
27 :return: same objetc but with lower case letters for FORTRAN code
28 """
29 for elem in self.iter():
30 if (not nonCode(elem)) and elem is not None and elem.text is not None:
31 elem.text = elem.text.lower()
32
33 @debugDecor
34 def indent(self, nodeToUpdate=None, indentProgramunit=0, indentBranch=2,
35 exclDirectives=None):
36 """
37 :param nodeToUpdate: if None, the entire object is indented
38 :param indentProgramunit: number of space characters inside program unit
39 :param indentBranch: number of space characters fr other branches (do, if...)
40 :param exclDirectives: some lines are directives and must stay unindented. The cpp
41 directives are automatically recognized by fxtran but others
42 appear as FORTRAN comments and must be indentified here. This
43 option can take the following values:
44 - None: to recognize as directives the lines begining
45 with '!$OMP' (default)
46 - []: to suppress the exclusion
47 - [...]: to give another list of line beginings to consider
48 :return: same object but with indentation corrected
49 """
50
51 if nodeToUpdate is None:
52 nodeToUpdate = self
53
54 if exclDirectives is None:
55 exclDirectives = ['!$OMP']
56
57 def setLevel(elem, level, nextElem):
58 """
59 :param elem: element whose tail must be modifies
60 :param level: level of indentation
61 :param nextElem: next element
62 """
63 if elem.tail is not None:
64 elem.tail = elem.tail.replace('\t', ' ')
65 excl = (nextElem is not None and
66 (tag(nextElem) == 'cpp' or
67 tag(nextElem) == 'C' and
68 any(nextElem.text.startswith(d) for d in exclDirectives)))
69 if not excl:
70 elem.tail = re.sub('\n[ ]*', '\n' + ' ' * level, elem.tail)
71
72 def indentRecur(elem, level, inConstruct):
73 """
74 :param elem: dom element
75 :param level: current level for elem
76 :param inConstruct: True if we are inside a construct
77 """
78 blocs = ['file', 'program-unit', 'if-block', 'where-block', 'selectcase-block']
79 progstmt = ['subroutine-stmt', 'program-stmt', 'module-stmt', 'function-stmt',
80 'submodule-stmt', 'procedure-stmt', 'interface-stmt']
81 endprogstmt = ['end-' + s for s in progstmt]
82 interbranchstmt = ['else-stmt', 'else-if-stmt', 'else-where-stmt']
83 branchstmt = ['if-then-stmt', 'where-construct-stmt'] + interbranchstmt
84 endbranchstmt = ['end-if-stmt', 'end-where-stmt']
85
86 currlevel = level
87 laste = None
88 firstnumselect = True
89 for ie, sElem in enumerate(elem):
90 # Indentation does not apply to these lines (eg SUBROUTINE statement, DO construct)
91 # but apply to the lines inside
92 if tag(sElem) in progstmt:
93 currlevel += indentProgramunit
94 elif tag(sElem) in branchstmt + [inConstruct + '-stmt']:
95 currlevel += indentBranch
96
97 # Add indentation *to the tail*, thus for the next line
98 setLevel(sElem, currlevel, elem[ie + 1] if ie + 1 < len(elem) else None)
99
100 if tag(elem) == 'selectcase-construct':
101 # Structure is:
102 # <selectcase-construct>
103 # <selectcase-block><select-case-stmt>SELECT
104 # CASE (...)</select-case-stmt> \n +2
105 # </selectcase-block>
106 # <selectcase-block><case-stmt>CASE<case-selector>(...)
107 # </case-selector></case-stmt> \n +4
108 # statement \n +4
109 # statement \n +2
110 # </selectcase-block>
111 # <selectcase-block><case-stmt>CASE<case-selector>(...)
112 # </case-selector></case-stmt> \n +4
113 # statement \n +4
114 # statement \n +0
115 # <end-select-case-stmt>END SELECT</end-select-case-stmt>
116 # </selectcase-block></selectcase-construct>
117 if firstnumselect:
118 firstnumselect = False
119 else:
120 # previous line was a CASE line, we must indent it only once
121 # pylint: disable-next=unsubscriptable-object
122 setLevel(laste[-1], level + indentBranch, sElem)
123 # statements are indented twice
124 indentRecur(sElem, level + indentBranch * 2, "")
125 if tag(sElem[-1]) == 'end-select-case-stmt':
126 setLevel(sElem[-2], level, sElem[-1])
127
128 elif tag(sElem) in blocs or tag(sElem).endswith('-construct'):
129 # This xml tag contains other tags, we iterate on them
130 if tag(sElem[0]) in interbranchstmt:
131 # Structure is <if-construct><if-block><if-then-stmt>IF...
132 # ...THEN</if-then-stmt>
133 # statement (the identation of the ELSE line is
134 # in the tail of this stetement)
135 # </if-block><if-block><else-stmt>ELSE</else-stmt>
136 # statement
137 # <end-if-stmt>ENDIF</end-if-stmt></if-block></if-construct>
138 # pylint: disable-next=unsubscriptable-object
139 setLevel(laste[-1], level, sElem)
140 construct = tag(sElem)[:-10] if tag(sElem).endswith('-construct') else ""
141 indentRecur(sElem, currlevel, construct)
142
143 # This line contains the end statement, we must remove the indentation contained
144 # in the tail of the previous item
145 if tag(sElem) in endprogstmt + endbranchstmt + ['end-' + inConstruct + '-stmt']:
146 setLevel(laste, level, sElem)
147 laste = sElem
148
149 indentRecur(nodeToUpdate, 0, "")
150 return nodeToUpdate
151
152 @debugDecor
154 """
155 Remove empty lines
156 """
157 elem = self.find('{*}file')
158 if elem is not None and elem.text is not None:
159 elem.text = elem.text.replace('\n', '')
160 for elem in self.iter():
161 if elem.tail is not None and '\n' in elem.tail:
162 elem.tail = elem.tail.replace('\t', ' ')
163 elem.tail = re.sub(r"\n[  \n]*\n", r"\n", elem.tail)
164
165 @debugDecor
166 def removeComments(self, exclDirectives=None, pattern=None):
167 """
168 :param exclDirectives: some lines are directives and must stay unindented. The cpp
169 directives are automatically recognized by fxtran but others
170 appear as FORTRAN comments and must be indentified here. This
171 option can take the following values:
172 - None: to recognize as directives the lines begining
173 with '!$OMP', '!$mnh', '!$acc' or '!$ACC' (default)
174 - []: to suppress the exclusion
175 - [...]: to give another list of line beginings to consider
176 :param pattern: remove only comments matching the pattern (string or re.compile output)
177 """
178 if exclDirectives is None:
179 exclDirectives = ['!$OMP', '!$mnh', '!$ACC', '!$acc']
180
181 if isinstance(pattern, str):
182 pattern = re.compile(pattern)
183
184 def recur(elem):
185 tailUpper = None
186 for ie in range(len(elem))[::-1]: # Loop from the end to the begining
187 sElem = elem[ie]
188 if tag(sElem) == 'C' and \
189 not any(sElem.text.startswith(d) for d in exclDirectives) and \
190 (pattern is None or pattern.match(sElem.text)):
191 # Don't loose the tail (containing new line character and indentation)
192 if ie != 0:
193 # It exists an element before,
194 # we add the current tail to this previsous element
195 if elem[ie - 1].tail is None:
196 elem[ie - 1].tail = sElem.tail
197 elif sElem.tail is not None:
198 elem[ie - 1].tail += sElem.tail
199 else:
200 # The's no previsous element, tail is givent back the container element
201 tailUpper = sElem.tail
202 elem.remove(sElem)
203 if len(sElem) >= 1:
204 tail = recur(sElem) # recursive call to inner elements
205 if tail is not None:
206 # The first element was a comment,
207 # its tail must be added to the text attribute
208 if sElem.text is None:
209 sElem.text = tail
210 else:
211 sElem.text += tail
212 return tailUpper
213 recur(self)
214
215 @debugDecor
216 def updateContinuation(self, nodeToUpdate=None, align=True,
217 removeALL=False, addBegin=True, removeBegin=False):
218 """
219 :param nodeToUpdate: if None, the entire xml is updated
220 :param align: True to align begin of continued lines
221 :param removeALL: True to suppress all the continuation line characters ('&')
222 :param addBegin: True to add missing continuation line characters ('&')
223 at the begining of lines
224 :param removeBegin: True to suppress continuation line characters ('&')
225 at the begining of lines
226
227 When suppressed, the '&' are replaced by a space character
228 Comments after a '&' are lost
229 """
230
231 assert not (align and removeALL), "We cannot remove and align at the same time"
232 assert not (addBegin and (removeALL or removeBegin)), \
233 "We cannot remove and add, at the same time, continuation characters"
234
235 if nodeToUpdate is None:
236 nodeToUpdate = self
237
238 parents = {} # cache to be used in recurDirect
239
240 def recurReverse(elem, tail):
241 for ie in range(len(elem))[::-1]: # Loop from the end to the begining
242 sElem = elem[ie]
243 parents[sElem] = elem
244 if tag(sElem) == 'cnt':
245 # Search for comments or cpp after the cnt node
246 commentsAfter = []
247 j = ie + 1
248 while j < len(elem) and tag(elem[j]) in ('C', 'cpp'):
249 commentsAfter.append(elem[j])
250 j += 1
251 nextNode = elem[j] if j < len(elem) else None
252
253 # Is it a '&' at the end of a line (or at the begining)?
254 isend = ((sElem.tail is not None and '\n' in sElem.tail) or
255 len(commentsAfter) > 0)
256
257 # Add missing continuation character at the begining of line
258 if isend and addBegin:
259 if sElem.tail is not None and \
260 sElem.tail.replace('\n', '').replace('\t', '').lstrip(' ') != '':
261 # tail contains text, probably an endding ')', after a carriage return
262 # Thus, there is no '&' to begin line
263 new = createElem('cnt', text='&')
264 # '&' must be put before any text on the following line containing code
265 i = 0
266 while sElem.tail[i] in (' ', '\n', '\t'):
267 i += 1
268 new.tail = ' ' + sElem.tail[i:]
269 sElem.tail = sElem.tail[:i]
270 elem.insert(ie + 1, new)
271 elif tag(nextNode) != 'cnt':
272 # There is no '&' to begin next line
273 new = createElem('cnt', text='&')
274 if len(commentsAfter) > 0:
275 # '&' must be put before any text on the following
276 # line containing code
277 i = 0
278 while i < len(commentsAfter[-1].tail) and \
279 commentsAfter[-1].tail[i] in (' ', '\n', '\t'):
280 i += 1
281 new.tail = ' ' + commentsAfter[-1].tail[i:]
282 commentsAfter[-1].tail = commentsAfter[-1].tail[:i]
283 else:
284 new.tail = ' '
285 elem.insert(ie + 1 + len(commentsAfter), new)
286
287 # Suppression
288 if removeALL or (removeBegin and not isend):
289 cpp = False
290 for com in commentsAfter[::-1]:
291 if tag(com) != 'cpp':
292 elem.remove(com)
293 else:
294 cpp = True
295 if not cpp:
296 # We cannot remove a continuation line followed by a cpp
297 elem.remove(sElem) # OK because we loop in reverse order
298 if sElem.tail is not None:
299 txt = sElem.tail.strip() + ' '
300 else:
301 txt = ' '
302 if ie != 0:
303 if elem[ie - 1].tail is None:
304 elem[ie - 1].tail = txt
305 else:
306 elem[ie - 1].tail += txt
307
308 # Recursively enter blocs
309 if len(sElem) >= 1:
310 recurReverse(sElem, tail)
311
312 def recurDirect(elem, ct, inCnt):
313 """
314 :param ct: current text
315 :param inCnt: -1 if we are not in a statement spanning several lines
316 elswhere contains the number of spaces to add
317 """
318 ignoreComment = False
319 if align:
320 for ie, sElem in enumerate(list(elem)):
321 # It is a '&' character marking the end of the line
322 isendcnt = tag(sElem) == 'cnt' and \
323 ((sElem.tail is not None and '\n' in sElem.tail) or
324 (ie + 1 < len(elem) and tag(elem[ie + 1]) == 'C'))
325 ignoreComment = (ignoreComment or
326 (isendcnt and
327 (ie + 1 < len(elem) and tag(elem[ie + 1]) == 'C')))
328
329 # REAL :: X1, & !comment 1
330 # !comment 2
331 # X2, &
332 # #ifdef XXX
333 # X3, &
334 # #endif
335 # X4
336 if isendcnt or ignoreComment or (inCnt != -1 and tag(sElem) == 'cpp'):
337 # Number of spaces for alignment not already determined
338 # (first line of the continuation)
339 if isendcnt and inCnt == -1:
340 # Search for the container statement
341 topstmt = elem
342 while not tag(topstmt).endswith('-stmt'):
343 topstmt = parents[topstmt]
344
345 # Character to align on
346 if tag(topstmt) == 'a-stmt':
347 patList = ('=>', '=', r'\‍(')
348 elif tag(topstmt) == 'call-stmt':
349 patList = (r'\‍(', r'call[ ]+\w', 'call ', 'call')
350 elif tag(topstmt) == 'if-stmt':
351 patList = (r'\‍(', r'\‍)', 'if ', 'if')
352 elif tag(topstmt) == 'where-stmt':
353 patList = (r'\‍(', r'\‍)', 'where ', 'where')
354 elif tag(topstmt) == 'forall-stmt':
355 patList = (r'\‍(', r'\‍)', 'forall ', 'forall')
356 elif tag(topstmt) == 'namelist-stmt':
357 patList = ('/.*/', '/', 'namelist')
358 elif tag(topstmt) == 'subroutine-stmt':
359 patList = (r'\‍(', r'subroutine[ ]+\w', 'subroutine ', 'subroutine')
360 elif tag(topstmt) == 'use-stmt':
361 patList = (':', r'use[ ]+\w', 'use ', 'use')
362 elif tag(topstmt) == 'T-decl-stmt':
363 patList = ('::', r'\w,', r'\w ', r'\w')
364 elif tag(topstmt) == 'print-stmt':
365 patList = ('print', )
366 elif tag(topstmt) == 'write-stmt':
367 patList = (r'\‍)', r'write[ ]*\‍(', 'write[ ]*', 'write')
368 elif tag(topstmt) == 'procedure-stmt':
369 patList = ('module[ ]+procedure[ ]*', 'module[ ]*', 'module')
370 else:
371 patList = ('::', ':', r'\‍(', '=>', '=', '[', ':', '/')
372
373 # Compute indentation value
374 inCnt = None
375 for pat in patList:
376 if inCnt is None:
377 mat = re.search(pat, ct, flags=re.IGNORECASE)
378 if mat is not None:
379 if ie + 1 < len(elem) and tag(elem[ie + 1]) != 'cnt':
380 # If there is no continuation character at the begining,
381 # align the text with the position after the delimiter
382 # found
383 inCnt = mat.end()
384 else:
385 inCnt = mat.end() - 1
386 if inCnt is None:
387 inCnt = 4
388
389 # Align the next line exept if it is a cpp line
390 if not (ie + 1 < len(elem) and tag(elem[ie + 1]) == 'cpp'):
391 if sElem.tail is not None:
392 sElem.tail = re.sub('\n[ ]*', '\n' + ' ' * inCnt, sElem.tail)
393 else:
394 sElem.tail = '\n' + ' ' * inCnt
395
396 if tag(sElem) not in ('C', 'cnt'):
397 ct += (sElem.text if sElem.text is not None else '')
398 ignoreComment = False
399
400 # Recursively enter the inner blocks
401 if len(sElem) >= 1:
402 ct, inCnt = recurDirect(sElem, ct, inCnt)
403
404 # Text after the end of block
405 ct += (sElem.tail if sElem.tail is not None else '')
406 if '\n' in ct:
407 ct = ct.split('\n')[-1]
408 if tag(sElem) not in ('cnt', 'C', 'cpp'):
409 inCnt = -1
410
411 return ct, inCnt
412
413 recurReverse(nodeToUpdate, 0)
414 recurDirect(nodeToUpdate, "", -1)
415 return nodeToUpdate
416
417 __NO_VALUE__ = '__NO_VALUE__'
418
419 @debugDecor
420 def updateSpaces(self, beforeOp=1, afterOp=1, inOperator=True,
421 beforeComma=0, afterComma=1,
422 beforeParenthesis=0, afterParenthesis=0,
423 beforeAffectation=1, afterAffectation=1, inAffectation=True,
424 beforeRangeDelim=0, afterRangeDelim=0,
425 beforeUseDelim=0, afterUseDelim=1,
426 beforeDeclDelim=1, afterDeclDelim=1,
427 inDeclDelim=True, afterTypeDecl=1,
428 beforeEqDo=0, afterEqDo=0,
429 beforeEqCall=0, afterEqCall=0,
430 beforeEqInit=0, afterEqInit=0,
431 beforeEndcnt=1, afterBegincnt=1,
432 afterIfwherecase=1, beforeThen=1, beforeIfaction=1,
433 afterProgunit=1,
434 endOfLine=True, afterName=0, inName=True,
435 beforeCmdsep=0, afterCmdsep=1,
436 adjacentKeywords=__NO_VALUE__, afterKeywords=__NO_VALUE__):
437 """
438 :param beforeOp, afterOp: number of spaces before and after operators
439 :param inOperator: True to suppress spaces in operators
440 :param beforeComma, afterComma: number of spaces before and after commas
441 :param beforeParenthesis, afterParenthesis: number of spaces before and after parenthesis
442 :param beforeAffectation, afterAffectation: number of spaces before and after
443 affectations or associations
444 :param inAffectation: True to suppress spaces in affectations and in association ('= >')
445 :param beforeRangeDelim, afterRangeDelim: number of spaces before and after range delimiters
446 :param beforeUseDelim, afterUseDelim: number of spaces before and after use delimiters (':')
447 :param beforeDeclDelim, afterDeclDelim: number of spaces before and after declaration and
448 enumerator delimiter ('::')
449 :param inDeclDelim: True to suppress spaces in declaration and enumerator delimiter (': :')
450 :param afterTypeDecl: number of spaces after the type in a declaration w/o '::'
451 (e.g. 'INTEGER I'); also for enumerators (minimum 1)
452 :param beforeEqDo, afterEqDo: number of spaces before and after '=' sign in DO and
453 FORALL statements
454 :param beforeEqCall, afterEqCall: number of spaces before and after '=' sign
455 in CALL statement
456 :param beforeEqInit, afterEqInit: number of spaces before and after '=' sign for init values
457 :param beforeEndcnt, afterBegincnt: number of spaces before a continuation chararcter at the
458 end of the line and after a continuation character
459 at the begining of a line
460 :param afterIfwherecase: number of spaces after the IF, ELSEIF, WHERE, ELSEWHERE,
461 SELECTCASE, CASE and FORALL keywords
462 :param beforeThen: number of spaces before the THEN keyword
463 :param beforeIfaction: number of spaces
464 between IF condition and action in one-line IF statement and
465 between FORALL specification and affectation in one-line FORALL
466 statement and
467 between WHERE mask and action in one-line WHERE statement
468 :param afterProgunit: between the program unit type (e.g. SUBROUTINE) and its name
469 :param endOfLine: True to suppress spaces at the end of the line
470 :param afterName: number of spaces after an indentifier, type or attribute name
471 :param inName: True to suppress spaces in identifier names
472 :param beforeCmdsep, afterCmdsep: number of spaces before and after command separator (';')
473 :param adjacentKeywords: describes the number of spaces to introduce between adjancent
474 keywords when this is legal (the list comes from the table
475 "6.2 Adjacent keywords where separating blanks are optional" of the
476 F2008 norm and has been complemented by "end select",
477 "implicit none" and "module procedure"; for the last two,
478 a minimum of 1 is required).
479 The allowed dictionnary keys are:
480 - block_data
481 - double_precision
482 - else_if
483 - else_where
484 - end_associate
485 - end_block
486 - end_block_data
487 - end_critical
488 - end_do
489 - end_enum
490 - end_file
491 - end_forall
492 - end_function
493 - end_if
494 - end_interface
495 - end_module
496 - end_procedure
497 - end_program
498 - end_selec
499 - end_select
500 - end_submodule
501 - end_subroutine
502 - end_team
503 - end_type
504 - end_where
505 - go_to
506 - in_out
507 - select_case
508 - select_type
509 - implicit_none
510 - module_procedure
511 For example, use {'end_do':1} to write 'END DO' or
512 {'end_do':0} to write 'ENDDO' or
513 {'end_do':None} to not update the writting
514 or use adjacentKeywords=None to disable everything
515 :param afterKeywords: describes the number of spaces to introduce after keywords.
516 Some keywords need a more sophisticated treatment and are controled
517 by specific keys (e.g. CASE).
518 The keys are the keyword in lowercase, some names can be tricky
519 to guess (e.g. the key for ENDFILE is 'end-file'). By default
520 only a few are defined.
521 Use afterKeywords=None to disable everything.
522
523 To not update spaces, put None instead of an integer and False in booleans.
524 For example, to not change number of spaces after a comma, use afterComma=None
525
526 Updates are done in the following order:
527 """
528
529 adjaKeyDesc = {
530 'block_data': (1, './/{*}block-data-stmt'),
531 'double_precision': (1, './/{*}intrinsic-T-spec/{*}T-N'),
532 'else_if': (1, './/{*}else-if-stmt'),
533 'else_where': (0, './/{*}else-where-stmt'),
534 'end_associate': (1, './/{*}end-associate-stmt'),
535 'end_block': (1, './/{*}end-block-stmt'),
536 'end_block_data': (1, './/{*}end-block-data-stmt'),
537 'end_critical': (1, './/{*}end-critical-stmt'),
538 'end_do': (1, './/{*}end-do-stmt'),
539 'end_enum': (1, './/{*}end-enum-stmt'),
540 'end_file': (1, './/{*}end-file-stmt'),
541 'end_forall': (1, './/{*}end-forall-stmt'),
542 'end_function': (1, './/{*}end-function-stmt'),
543 'end_if': (1, './/{*}end-if-stmt'),
544 'end_interface': (1, './/{*}end-interface-stmt'),
545 'end_module': (1, './/{*}end-module-stmt'),
546 'end_procedure': (1, './/{*}end-procedure-stmt'),
547 'end_program': (1, './/{*}end-program-stmt'),
548 'end_selec': (1, './/{*}end-select-case-stmt'),
549 'end_select': (1, './/{*}end-select-T-stmt'),
550 'end_submodule': (1, './/{*}end-submodule-stmt'),
551 'end_subroutine': (1, './/{*}end-subroutine-stmt'),
552 'end_team': (1, './/{*}end-change-team-stmt'),
553 'end_type': (1, './/{*}end-T-stmt'),
554 'end_where': (1, './/{*}end-where-stmt'),
555 'go_to': (0, './/{*}goto-stmt'),
556 'in_out': (0, './/{*}intent-spec'),
557 'select_case': (1, './/{*}select-case-stmt'),
558 'select_type': (1, './/{*}select-T-stmt'),
559 'implicit_none': (1, './/{*}implicit-none-stmt'),
560 'module_procedure': (1, './/{*}procedure-stmt'),
561 }
562
563 afterKey = {
564 'print': 0,
565 'call': 1,
566 'use': 1,
567 'do': 1,
568 'end-file': 1,
569 'save': 1,
570 }
571
572 assert adjacentKeywords is None or adjacentKeywords == self.__NO_VALUE__ or \
573 all(k in adjaKeyDesc
574 for k in adjacentKeywords), "Unknown key in **adjacentKeywords"
575
576 def getvalAdja(key):
577 if adjacentKeywords is None:
578 return None
579 if adjacentKeywords == self.__NO_VALUE__:
580 return adjaKeyDesc[key][0]
581 return adjacentKeywords.get(key, adjaKeyDesc[key][0])
582
583 def getvalAfter(key):
584 key = key[:-5]
585 if afterKeywords != self.__NO_VALUE__:
586 num = afterKeywords.get(key, afterKey.get(key, None))
587 else:
588 num = afterKey.get(key, None)
589 return num
590
591 assert afterProgunit is None or afterProgunit >= 1
592 assert afterTypeDecl is None or afterTypeDecl >= 1
593 for k in ('implicit_none', 'module_procedure'):
594 num = getvalAdja(k)
595 assert num is None or num >= 1, \
596 "adjacentKeywords['" + k + "'] must be at least 1 (is " + str(num) + ")"
597 for k in ('use', 'call', 'end-file', 'do'):
598 num = getvalAfter(k + '-stmt')
599 assert num is None or num >= 1, \
600 "afterKeywords['" + k + "'] must be at least 1 (is " + str(num) + ")"
601
602 for elem in self.iter():
603 isNotC = tag(elem) != 'C'
604 # security
605 if elem.tail is None:
606 elem.tail = ""
607 elem.tail = elem.tail.replace('\t', ' ')
608
609 # Around parenthesis
610 if beforeParenthesis is not None:
611 elem.tail = re.sub(r"[  ]*\‍(", " " * beforeParenthesis + r"(", elem.tail)
612 elem.tail = re.sub(r"[  ]*\‍)", " " * beforeParenthesis + r")", elem.tail)
613 if elem.text is not None and isNotC:
614 elem.text = re.sub(r"[  ]*\‍(", " " * beforeParenthesis + r"(", elem.text)
615 elem.text = re.sub(r"[  ]*\‍)", " " * beforeParenthesis + r")", elem.text)
616 if afterParenthesis is not None:
617 elem.tail = re.sub(r"\‍([  ]*", "(" + " " * afterParenthesis, elem.tail)
618 elem.tail = re.sub(r"\‍)[  ]*", ")" + " " * afterParenthesis, elem.tail)
619 if elem.text is not None and isNotC:
620 elem.text = re.sub(r"\‍([  ]*", "(" + " " * afterParenthesis, elem.text)
621 elem.text = re.sub(r"\‍)[  ]*", ")" + " " * afterParenthesis, elem.text)
622
623 # Around commas
624 if beforeComma is not None:
625 elem.tail = re.sub(r"[  ]*,", " " * beforeComma + r",", elem.tail)
626 if elem.text is not None and isNotC:
627 elem.text = re.sub(r"[  ]*,", " " * beforeComma + r",", elem.text)
628 if afterComma is not None:
629 elem.tail = re.sub(r",[  ]*", "," + " " * afterComma, elem.tail)
630 if elem.text is not None and isNotC:
631 elem.text = re.sub(r",[  ]*", "," + " " * afterComma, elem.text)
632
633 # End of line
634 if endOfLine:
635 elem.tail = re.sub(r"[  ]*\n", r"\n", elem.tail)
636
637 # In names or around names (identifier, type, attribute)
638 if tag(elem) in ('N', 'T-N', 'attribute-N'):
639 if inName:
640 for nnn in elem.findall('{*}n'):
641 if nnn.tail is not None:
642 nnn.tail = nnn.tail.strip(' ')
643 if elem.tail is not None and afterName is not None:
644 elem.tail = ' ' * afterName + elem.tail.lstrip(' ')
645
646 # Around range delimiter
647 elif tag(elem) == 'lower-bound' and elem.tail is not None and ':' in elem.tail:
648 if beforeRangeDelim is not None:
649 elem.tail = ' ' * beforeRangeDelim + elem.tail.lstrip(' ')
650 if afterRangeDelim is not None:
651 elem.tail = elem.tail.rstrip(' ') + ' ' * beforeRangeDelim
652
653 # Around ':' in USE statements
654 elif tag(elem) == 'module-N' and elem.tail is not None and ':' in elem.tail:
655 if beforeUseDelim is not None:
656 elem.tail = re.sub(r"[  ]*:", " " * beforeUseDelim + r":", elem.tail)
657 if afterUseDelim is not None:
658 elem.tail = re.sub(r":[  ]*", ":" + " " * afterUseDelim, elem.tail)
659
660 # Around and in '::' in declaration statements
661 # After the type in a declaration
662 elif tag(elem) in ('attribute', '_T-spec_') and elem.tail is not None:
663 if inDeclDelim:
664 elem.tail = re.sub(r":[  ]*:", r"::", elem.tail)
665 if beforeDeclDelim is not None:
666 elem.tail = re.sub(r"[ ]*(:[  ]*:)", ' ' * beforeDeclDelim + r"\1", elem.tail)
667 if afterDeclDelim is not None:
668 elem.tail = re.sub(r"(:[  ]*:)[ ]*", r"\1" + ' ' * afterDeclDelim, elem.tail)
669 if tag(elem) == '_T-spec_' and afterTypeDecl is not None:
670 elem.tail = elem.tail.rstrip(' ') + ' ' * afterTypeDecl
671
672 # Around and in '::' in enumerators
673 # After the enumerator keyword
674 elif tag(elem) == 'enumerator-stmt' and elem.text is not None:
675 if ':' in elem.text:
676 if inDeclDelim:
677 elem.text = re.sub(r":[  ]*:", r"::", elem.text)
678 if beforeDeclDelim is not None:
679 elem.text = re.sub(r"[ ]*(:[  ]*:)", ' ' * beforeDeclDelim + r"\1",
680 elem.text)
681 if afterDeclDelim is not None:
682 elem.text = re.sub(r"(:[  ]*:)[ ]*", r"\1" + ' ' * afterDeclDelim,
683 elem.text)
684 elif afterTypeDecl is not None:
685 elem.text = elem.text.rstrip(' ') + ' ' * afterTypeDecl
686
687 # Between the program unit type and its name
688 elif (tag(elem) in ('subroutine-stmt', 'program-stmt', 'module-stmt', 'function-stmt',
689 'submodule-stmt', 'procedure-stmt', 'interface-stmt',
690 'end-subroutine-stmt', 'end-program-stmt',
691 'end-module-stmt', 'end-function-stmt',
692 'end-submodule-stmt', 'end-procedure-stmt', 'end-interface-stmt')
693 and afterProgunit is not None):
694 if elem.text is not None:
695 elem.text = elem.text.rstrip(' ') + ' ' * afterProgunit
696
697 # Around '=' sign in DO and FORALL statements
698 elif tag(elem) in ('do-V', 'V') and elem.tail is not None and '=' in elem.tail:
699 if beforeEqDo is not None:
700 elem.tail = re.sub('[ ]*=', ' ' * beforeEqDo + '=', elem.tail)
701 if afterEqDo is not None:
702 elem.tail = re.sub('=[ ]*', '=' + ' ' * beforeEqDo, elem.tail)
703
704 # Around '=' sign in CALL statements
705 elif tag(elem) == 'arg-N' and elem.tail is not None and '=' in elem.tail:
706 if beforeEqCall is not None:
707 elem.tail = re.sub('[ ]*=', ' ' * beforeEqCall + '=', elem.tail)
708 if afterEqCall is not None:
709 elem.tail = re.sub('=[ ]*', '=' + ' ' * beforeEqCall, elem.tail)
710
711 # Around '=' sign for init values
712 elif (tag(elem) in ('EN-N', 'named-constant') and
713 elem.tail is not None and '=' in elem.tail):
714 if beforeEqInit is not None:
715 elem.tail = re.sub('[ ]*=', ' ' * beforeEqInit + '=', elem.tail)
716 if afterEqInit is not None:
717 elem.tail = re.sub('=[ ]*', '=' + ' ' * beforeEqInit, elem.tail)
718 # Around the command separator ';'
719 elif tag(elem) == 'smc':
720 if beforeCmdsep is not None:
721 prev = self.getSiblings(elem, after=False)
722 if len(prev) != 0 and prev[-1].tail is not None:
723 prev[-1].tail = ' ' * beforeCmdsep + prev[-1].tail.lstrip(' ')
724 if afterCmdsep is not None and elem.tail is not None:
725 elem.tail = elem.tail.rstrip(' ') + ' ' * afterCmdsep
726
727 # Around and in association operators (affectation case done after)
728 elif tag(elem) == 'associate-N' and elem.tail is not None and '=' in elem.tail:
729 if beforeAffectation is not None:
730 elem.tail = re.sub('[ ]*=', ' ' * beforeAffectation + '=', elem.tail)
731 if afterAffectation is not None:
732 elem.tail = re.sub('>[ ]*', '>' + ' ' * beforeAffectation, elem.tail)
733 if inAffectation:
734 elem.tail = re.sub(r'=[ ]*>', '=>', elem.tail)
735
736 # After a reserved keyword
737 # elif afterKeywords is not None and tag(elem).endswith('-stmt'):
738 # num = getvalAfter(tag(elem))
739 # if num is not None and elem.text is not None:
740 # elem.text = elem.text.rstrip(' ') + ' ' * num
741
742 # Another loop on elements
743 # All the transformations are not put in a single loop because the following one act
744 # on sub-elements. Putting them all in the same loop would prevent to control in which order
745 # the different transformations occur.
746 # For instance, the suppression on the space after the parenthesis must be done before
747 # the adding of a space before a THEN keyword
748 for elem in self.iter():
749 # Around and in operators
750 if tag(elem) == 'op-E': # op are always (?) in op-E nodes
751 for op in elem.findall('{*}op'):
752 if beforeOp is not None:
753 io = list(elem).index(op)
754 if io != 0:
755 prev = elem[io - 1]
756 if prev.tail is None:
757 prev.tail = ' ' * beforeOp
758 else:
759 prev.tail = prev.tail.rstrip(' ') + ' ' * beforeOp
760 if afterOp is not None:
761 if op.tail is None:
762 op.tail = ' ' * afterOp
763 else:
764 op.tail = op.tail.lstrip(' ') + ' ' * afterOp
765 if inOperator:
766 for oo in op.findall('{*}o'):
767 if oo.tail is not None:
768 oo.tail = oo.tail.strip(' ')
769
770 # Around and in affectation operators (association case done before)
771 elif tag(elem) in ('a-stmt', 'pointer-a-stmt'):
772 # a are always (?) in a-stmt or pointer-a-stmt nodes
773 for aff in elem.findall('{*}a'):
774 if beforeAffectation is not None:
775 prev = elem[list(elem).index(aff) - 1]
776 if prev.tail is None:
777 prev.tail = ' ' * beforeAffectation
778 else:
779 prev.tail = prev.tail.rstrip(' ') + ' ' * beforeAffectation
780 if afterAffectation is not None:
781 if aff.tail is None:
782 aff.tail = ' ' * afterAffectation
783 else:
784 aff.tail = aff.tail.lstrip(' ') + ' ' * afterAffectation
785 if inAffectation:
786 aff.text = aff.text.replace(' ', '')
787
788 # After a IF, WHERE, ELSEIF, ELSEWHERE, SELECTCASE, CASE and FORALL keyword,
789 # and before THEN keyword
790 elif tag(elem) in ('if-stmt', 'if-then-stmt', 'else-if-stmt',
791 'where-stmt', 'where-construct-stmt', 'else-where-stmt',
792 'select-case-stmt', 'case-stmt',
793 'forall-stmt', 'forall-construct-stmt'):
794 if afterIfwherecase is not None and elem.text is not None:
795 if tag(elem) == 'case-stmt':
796 # the (eventual) parenthesis is not in the text of the node
797 elem.text = elem.text.rstrip(' ') + ' ' * afterIfwherecase
798 else:
799 elem.text = re.sub(r'[ ]*\‍(', ' ' * afterIfwherecase + '(',
800 elem.text, count=1)
801 if tag(elem) in ('if-then-stmt', 'else-if-stmt') and beforeThen is not None:
802 cond = elem.find('{*}condition-E')
803 cond.tail = re.sub(r'\‍)[ ]*([a-zA-Z]*$)', ')' + ' ' * beforeThen + r'\1',
804 cond.tail)
805 elif tag(elem) == 'if-stmt' and beforeIfaction is not None:
806 cond = elem.find('{*}condition-E')
807 cond.tail = re.sub(r'\‍)[ ]*$', ')' + ' ' * beforeIfaction, cond.tail)
808 elif tag(elem) == 'where-stmt' and beforeIfaction is not None:
809 cond = elem.find('{*}mask-E')
810 cond.tail = re.sub(r'\‍)[ ]*$', ')' + ' ' * beforeIfaction, cond.tail)
811 elif tag(elem) == 'forall-stmt' and beforeIfaction is not None:
812 sub = elem.find('{*}forall-triplet-spec-LT')
813 sub.tail = re.sub(r'\‍)[ ]*$', ')' + ' ' * beforeIfaction, sub.tail)
814
815 # Direct search to prevent using the costly getParent function
816 if beforeEndcnt is not None or afterBegincnt is not None:
817 for elem in self.findall('.//{*}cnt/..'): # node containing continuation characters
818 for cnt in elem.findall('{*}cnt'): # continuation characters
819 ic = list(elem).index(cnt)
820 if ic == 0:
821 # the string before the continuation character is in the parent text
822 prev = elem
823 pstring = prev.text
824 else:
825 # the string before the continuation character is in previsous sibling tail
826 prev = elem[ic - 1]
827 pstring = prev.tail
828 if '\n' in pstring and '\n' in cnt.tail:
829 # continuation character alone on a line
830 pass
831 elif '\n' in pstring and afterBegincnt is not None:
832 # continuation character at the begining of a line
833 cnt.tail = ' ' * afterBegincnt + cnt.tail.lstrip(' ')
834 elif beforeEndcnt is not None:
835 # continuation character at the end of a line
836 # (eventually followed by a comment)
837 if prev == elem:
838 prev.text = prev.text.rstrip(' ') + ' ' * beforeEndcnt
839 else:
840 prev.tail = prev.tail.rstrip(' ') + ' ' * beforeEndcnt
841
842 # In adjacent keywords
843 for key, val in adjaKeyDesc.items():
844 num = getvalAdja(key)
845 if num is not None:
846 for node in self.findall(val[1]):
847 lf = "[ ]*".join(["(" + p + ")" for p in key.split('_')])
848 repl = (" " * num).join([r"\{i}".format(i=i + 1)
849 for i, _ in enumerate(key.split('_'))])
850 node.text = re.sub(lf, repl, node.text, flags=re.IGNORECASE)
851
852 @debugDecor
853 def changeIfStatementsInIfConstructs(self, singleItem=None):
854 """
855 Convert if-stmt to if-then-stmt. If singleItem is not filled, conversion to entire
856 object is performed.
857 E.g., before :
858 IF(A=B) print*,"C
859 after :
860 IF(A=B) THEN
861 print*,"C
862 END IF
863 Conversion is not done if 'CYLE' is found in action-stmt
864 :param singleItem: single if-stmt; in case transformation is applied on one if-stmt only
865 """
866 if singleItem is not None:
867 ifstmt = [singleItem]
868 else:
869 ifstmt = self.findall('.//{*}if-stmt')
870 for item in ifstmt:
871 cycleStmt = item.findall('.//{*}cycle-stmt')
872 if len(cycleStmt) == 0:
873 # Get indentation from last sibling
874 par = self.getParent(item)
875 ind = par[:].index(item)
876 if ind != 0 and par[ind - 1].tail is not None:
877 # if tail of previous sibling exists
878 currIndent = len(par[ind - 1].tail) - len(par[ind - 1].tail.rstrip(' '))
879 else:
880 # no tail = no indentation
881 currIndent = 0
882
883 # Convert if-stmt into if-construct
884 # <if-stmt>IF(<condition-E>...</condition-E>) <f:action-stmt>...
885 # ...</f:action-stmt></f:if-stmt>
886 # <if-construct><if-block><if-then-stmt>IF(<f:condition-E>...
887 # ...</condition-E>) THEN</f:if-then-stmt>
888 # ...
889 # <f:end-if-stmt>ENDIF</f:end-if-stmt></f:if-block></f:if-construct>
890 # 1 create missing blocks
891 item.tag = f'{{{NAMESPACE}}}if-construct'
892 ifBlock = createElem('if-block')
893 ifThenStmt = createElem('if-then-stmt')
894 endif = createElem('end-if-stmt')
895 ifBlock.append(ifThenStmt)
896 item.append(ifBlock)
897 # 2 move 'IF(' text
898 ifThenStmt.text = item.text # copy 'IF(' text
899 ifThenStmt.tail = '\n' + (2 + currIndent) * ' ' # indentation for main statement
900 item.text = None # remove olf 'IF(' text
901 # 3 move condition and add THEN
902 condition = item.find('{*}condition-E')
903 if not condition.tail.endswith(' '):
904 condition.tail += ' '
905 condition.tail += 'THEN'
906 ifThenStmt.append(condition)
907 item.remove(condition)
908 # 4 move action
909 action = item.find('{*}action-stmt')
910 action[0].tail = '\n' + currIndent * ' ' # indentation for the ENDIF
911 ifBlock.append(action[0])
912 item.remove(action)
913 # 5 add ENDIF
914 endif.text = 'END IF'
915 ifBlock.append(endif)
916 # 6 remove any cnt which was directly in the if-stmt node
917 # (replaced by '\n' after THEN)
918 for cnt in item.findall('./{*}cnt'):
919 item.remove(cnt)
920
921 @debugDecor
923 """
924 Remove the CONTAINS statement if this section is empty
925 """
926 for contains in self.findall('.//{*}contains-stmt'):
927 par = self.getParent(contains)
928 index = list(par).index(contains)
929 nextStmt = index + 1
930 while tag(par[nextStmt]) == 'C':
931 nextStmt += 1
932 if tag(par[nextStmt]) in ('end-subroutine-stmt', 'end-function-stmt',
933 'end-module-stmt'):
934 # CONTAINS bloc is empty
935 par.remove(contains)
updateContinuation(self, nodeToUpdate=None, align=True, removeALL=False, addBegin=True, removeBegin=False)
Definition cosmetics.py:217
removeComments(self, exclDirectives=None, pattern=None)
Definition cosmetics.py:166
updateSpaces(self, beforeOp=1, afterOp=1, inOperator=True, beforeComma=0, afterComma=1, beforeParenthesis=0, afterParenthesis=0, beforeAffectation=1, afterAffectation=1, inAffectation=True, beforeRangeDelim=0, afterRangeDelim=0, beforeUseDelim=0, afterUseDelim=1, beforeDeclDelim=1, afterDeclDelim=1, inDeclDelim=True, afterTypeDecl=1, beforeEqDo=0, afterEqDo=0, beforeEqCall=0, afterEqCall=0, beforeEqInit=0, afterEqInit=0, beforeEndcnt=1, afterBegincnt=1, afterIfwherecase=1, beforeThen=1, beforeIfaction=1, afterProgunit=1, endOfLine=True, afterName=0, inName=True, beforeCmdsep=0, afterCmdsep=1, adjacentKeywords=__NO_VALUE__, afterKeywords=__NO_VALUE__)
Definition cosmetics.py:436
indent(self, nodeToUpdate=None, indentProgramunit=0, indentBranch=2, exclDirectives=None)
Definition cosmetics.py:35
changeIfStatementsInIfConstructs(self, singleItem=None)
Definition cosmetics.py:853