ยปCore Development>Code coverage>Tools/clinic/cpp.py

Python code coverage for Tools/clinic/cpp.py

#countcontent
1n/aimport re
2n/aimport sys
3n/a
4n/adef negate(condition):
5n/a """
6n/a Returns a CPP conditional that is the opposite of the conditional passed in.
7n/a """
8n/a if condition.startswith('!'):
9n/a return condition[1:]
10n/a return "!" + condition
11n/a
12n/aclass Monitor:
13n/a """
14n/a A simple C preprocessor that scans C source and computes, line by line,
15n/a what the current C preprocessor #if state is.
16n/a
17n/a Doesn't handle everything--for example, if you have /* inside a C string,
18n/a without a matching */ (also inside a C string), or with a */ inside a C
19n/a string but on another line and with preprocessor macros in between...
20n/a the parser will get lost.
21n/a
22n/a Anyway this implementation seems to work well enough for the CPython sources.
23n/a """
24n/a
25n/a is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
26n/a
27n/a def __init__(self, filename=None, *, verbose=False):
28n/a self.stack = []
29n/a self.in_comment = False
30n/a self.continuation = None
31n/a self.line_number = 0
32n/a self.filename = filename
33n/a self.verbose = verbose
34n/a
35n/a def __repr__(self):
36n/a return ''.join((
37n/a '<Monitor ',
38n/a str(id(self)),
39n/a " line=", str(self.line_number),
40n/a " condition=", repr(self.condition()),
41n/a ">"))
42n/a
43n/a def status(self):
44n/a return str(self.line_number).rjust(4) + ": " + self.condition()
45n/a
46n/a def condition(self):
47n/a """
48n/a Returns the current preprocessor state, as a single #if condition.
49n/a """
50n/a return " && ".join(condition for token, condition in self.stack)
51n/a
52n/a def fail(self, *a):
53n/a if self.filename:
54n/a filename = " " + self.filename
55n/a else:
56n/a filename = ''
57n/a print("Error at" + filename, "line", self.line_number, ":")
58n/a print(" ", ' '.join(str(x) for x in a))
59n/a sys.exit(-1)
60n/a
61n/a def close(self):
62n/a if self.stack:
63n/a self.fail("Ended file while still in a preprocessor conditional block!")
64n/a
65n/a def write(self, s):
66n/a for line in s.split("\n"):
67n/a self.writeline(line)
68n/a
69n/a def writeline(self, line):
70n/a self.line_number += 1
71n/a line = line.strip()
72n/a
73n/a def pop_stack():
74n/a if not self.stack:
75n/a self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
76n/a return self.stack.pop()
77n/a
78n/a if self.continuation:
79n/a line = self.continuation + line
80n/a self.continuation = None
81n/a
82n/a if not line:
83n/a return
84n/a
85n/a if line.endswith('\\'):
86n/a self.continuation = line[:-1].rstrip() + " "
87n/a return
88n/a
89n/a # we have to ignore preprocessor commands inside comments
90n/a #
91n/a # we also have to handle this:
92n/a # /* start
93n/a # ...
94n/a # */ /* <-- tricky!
95n/a # ...
96n/a # */
97n/a # and this:
98n/a # /* start
99n/a # ...
100n/a # */ /* also tricky! */
101n/a if self.in_comment:
102n/a if '*/' in line:
103n/a # snip out the comment and continue
104n/a #
105n/a # GCC allows
106n/a # /* comment
107n/a # */ #include <stdio.h>
108n/a # maybe other compilers too?
109n/a _, _, line = line.partition('*/')
110n/a self.in_comment = False
111n/a
112n/a while True:
113n/a if '/*' in line:
114n/a if self.in_comment:
115n/a self.fail("Nested block comment!")
116n/a
117n/a before, _, remainder = line.partition('/*')
118n/a comment, comment_ends, after = remainder.partition('*/')
119n/a if comment_ends:
120n/a # snip out the comment
121n/a line = before.rstrip() + ' ' + after.lstrip()
122n/a continue
123n/a # comment continues to eol
124n/a self.in_comment = True
125n/a line = before.rstrip()
126n/a break
127n/a
128n/a # we actually have some // comments
129n/a # (but block comments take precedence)
130n/a before, line_comment, comment = line.partition('//')
131n/a if line_comment:
132n/a line = before.rstrip()
133n/a
134n/a if not line.startswith('#'):
135n/a return
136n/a
137n/a line = line[1:].lstrip()
138n/a assert line
139n/a
140n/a fields = line.split()
141n/a token = fields[0].lower()
142n/a condition = ' '.join(fields[1:]).strip()
143n/a
144n/a if_tokens = {'if', 'ifdef', 'ifndef'}
145n/a all_tokens = if_tokens | {'elif', 'else', 'endif'}
146n/a
147n/a if token not in all_tokens:
148n/a return
149n/a
150n/a # cheat a little here, to reuse the implementation of if
151n/a if token == 'elif':
152n/a pop_stack()
153n/a token = 'if'
154n/a
155n/a if token in if_tokens:
156n/a if not condition:
157n/a self.fail("Invalid format for #" + token + " line: no argument!")
158n/a if token == 'if':
159n/a if not self.is_a_simple_defined(condition):
160n/a condition = "(" + condition + ")"
161n/a else:
162n/a fields = condition.split()
163n/a if len(fields) != 1:
164n/a self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
165n/a symbol = fields[0]
166n/a condition = 'defined(' + symbol + ')'
167n/a if token == 'ifndef':
168n/a condition = '!' + condition
169n/a
170n/a self.stack.append(("if", condition))
171n/a if self.verbose:
172n/a print(self.status())
173n/a return
174n/a
175n/a previous_token, previous_condition = pop_stack()
176n/a
177n/a if token == 'else':
178n/a self.stack.append(('else', negate(previous_condition)))
179n/a elif token == 'endif':
180n/a pass
181n/a if self.verbose:
182n/a print(self.status())
183n/a
184n/aif __name__ == '__main__':
185n/a for filename in sys.argv[1:]:
186n/a with open(filename, "rt") as f:
187n/a cpp = Monitor(filename, verbose=True)
188n/a print()
189n/a print(filename)
190n/a for line_number, line in enumerate(f.read().split('\n'), 1):
191n/a cpp.writeline(line)