PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
cpp.py
1"""
2This module implements the Cpp class containing the methods for dealing with cpp directives
3"""
4
5from pyfortool.util import debugDecor, alltext, PYFTError, tag, noParallel
6from pyfortool.tree import updateTree
7from pyfortool.variables import updateVarList
8
9
10class Cpp:
11 """
12 C preprocessor directive handling methods.
13
14 Provides utilities for evaluating and manipulating C preprocessor
15 (#ifdef, #ifndef, #else, #endif) directives in FORTRAN code.
16 """
17
18 @debugDecor
19 @noParallel
20 @updateVarList
21 @updateTree('signal')
22 def applyCPPifdef(self, keys):
23 """
24 Evaluate and reduce #ifdef / #ifndef blocks based on specified keys.
25
26 Parameters
27 ----------
28 keys : list of str
29 List of preprocessor keys to evaluate.
30 Keys preceded by '%' are treated as undefined (False).
31 Example: ['KEY1', '%KEY2'] means KEY1=True, KEY2=False.
32
33 Transformation Examples
34 ----------------------
35 If 'K' is in keys (K=True):
36 #ifdef K
37 A
38 #else
39 B
40 #endif
41 becomes simply: A
42
43 If '%K' is in keys (K=False):
44 #ifdef K
45 A
46 #else
47 B
48 #endif
49 becomes simply: B
50
51 Notes
52 -----
53 - Only handles #ifdef and #ifndef (not "#if defined ...")
54 - Nested if/ifdef/ifndef are supported
55 - Code not matching any conditional is kept unchanged
56 """
57
58 # We make the hypothesis that #ifdef, #else and #endif have the same parent
59 # Get all nodes containing #ifdef or #ifndef
60 parents = set(self.getParent(cppNode) for cppNode in self.findall('.//{*}cpp')
61 if (cppNode.text.startswith('#ifdef ') or
62 cppNode.text.startswith('#ifndef ')))
63
64 # Iteration over nodes contained in each parent
65 toRemove = []
66 for par in parents:
67 # we deal with nested #ifdef #ifndef and #if
68 # We need to track #if cpp directives to discard #else and #endif related to these #if
69 # Each time we enter an #ifdef, #ifndef or #if, we add a value to the keep list
70 # True or False to keep or discard it, None not to touch it
71 keep = [True]
72 for node in par:
73 if tag(node) == 'cpp':
74 if node.text.startswith('#ifdef '):
75 k = alltext(node).split(' ')[1].strip()
76 if k in keys:
77 toRemove.append((node, par))
78 keep.append(True)
79 elif '%' + k in keys:
80 toRemove.append((node, par))
81 keep.append(False)
82 else:
83 keep.append(None)
84 if False in keep:
85 toRemove.append((node, par))
86 elif node.text.startswith('#ifndef '):
87 k = alltext(node).split(' ')[1].strip()
88 if k in keys:
89 toRemove.append((node, par))
90 keep.append(False)
91 elif '%' + k in keys:
92 toRemove.append((node, par))
93 keep.append(True)
94 else:
95 keep.append(None)
96 if False in keep:
97 toRemove.append((node, par))
98 elif node.text.startswith('#if '):
99 if False in keep:
100 toRemove.append((node, par))
101 # We are in a #if,following #else / #endif is associated to this #if
102 keep.append(None)
103 elif node.text.startswith('#else'):
104 if keep[-1] is not None:
105 toRemove.append((node, par))
106 keep[-1] = not keep[-1]
107 elif False in keep:
108 toRemove.append((node, par))
109 elif node.text.startswith('#endif'):
110 if keep[-1] is not None or False in keep:
111 toRemove.append((node, par))
112 keep.pop()
113 elif node.text.startswith('#elifdef') or node.text.startswith('#elifndef'):
114 raise NotImplementedError("#elifdef and #elifndef not (yet?) implemented")
115 else:
116 if False in keep:
117 toRemove.append((node, par))
118 else:
119 if False in keep:
120 toRemove.append((node, par))
121 if len(keep) != 1:
122 # We check the hypothesis done at the beginning
123 raise PYFTError("#else or #endif hasn't the same parent as #ifdef " +
124 "or #ifndef in {f}".format(f=self.getFileName()))
125 # Suppress node in reverse order to attach tail to previous node
126 if len(toRemove) != 0:
127 self.tree.signal(self) # Tree may need to be updated
128 for node, par in toRemove[::-1]:
129 index = list(par).index(node)
130 if index != 0:
131 if node.tail is not None:
132 if par[index - 1].tail is None:
133 par[index - 1].tail = ""
134 # We only keep '\n' and spaces at the end (indentation)
135 par[index - 1].tail += (node.tail.count('\n') * '\n' +
136 (len(node.tail) - len(node.tail.rstrip(' '))) * ' ')
137 par.remove(node)
applyCPPifdef(self, keys)
Definition cpp.py:22