Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/c-analyzer/c_parser/preprocessor/gcc.py
12 views
1
import os.path
2
import re
3
4
from . import common as _common
5
6
7
TOOL = 'gcc'
8
9
META_FILES = {
10
'<built-in>',
11
'<command-line>',
12
}
13
14
# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
15
# flags:
16
# 1 start of a new file
17
# 2 returning to a file (after including another)
18
# 3 following text comes from a system header file
19
# 4 following text treated wrapped in implicit extern "C" block
20
LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"((?: [1234])*)$')
21
PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*')
22
COMPILER_DIRECTIVE_RE = re.compile(r'''
23
^
24
(.*?) # <before>
25
(__\w+__) # <directive>
26
\s*
27
[(] [(]
28
(
29
[^()]*
30
(?:
31
[(]
32
[^()]*
33
[)]
34
[^()]*
35
)*
36
) # <args>
37
( [)] [)] ) # <closed>
38
''', re.VERBOSE)
39
40
POST_ARGS = (
41
'-pthread',
42
'-std=c99',
43
#'-g',
44
#'-Og',
45
#'-Wno-unused-result',
46
#'-Wsign-compare',
47
#'-Wall',
48
#'-Wextra',
49
'-E',
50
)
51
52
53
def preprocess(filename,
54
incldirs=None,
55
includes=None,
56
macros=None,
57
samefiles=None,
58
cwd=None,
59
):
60
if not cwd or not os.path.isabs(cwd):
61
cwd = os.path.abspath(cwd or '.')
62
filename = _normpath(filename, cwd)
63
64
postargs = POST_ARGS
65
if os.path.basename(filename) == 'socketmodule.h':
66
# Modules/socketmodule.h uses pycore_time.h which needs Py_BUILD_CORE.
67
# Usually it's defined by the C file which includes it.
68
postargs += ('-DPy_BUILD_CORE=1',)
69
70
text = _common.preprocess(
71
TOOL,
72
filename,
73
incldirs=incldirs,
74
includes=includes,
75
macros=macros,
76
#preargs=PRE_ARGS,
77
postargs=postargs,
78
executable=['gcc'],
79
compiler='unix',
80
cwd=cwd,
81
)
82
return _iter_lines(text, filename, samefiles, cwd)
83
84
85
def _iter_lines(text, reqfile, samefiles, cwd, raw=False):
86
lines = iter(text.splitlines())
87
88
# The first line is special.
89
# The next two lines are consistent.
90
firstlines = [
91
f'# 0 "{reqfile}"',
92
'# 0 "<built-in>"',
93
'# 0 "<command-line>"',
94
]
95
if text.startswith('# 1 '):
96
# Some preprocessors emit a lineno of 1 for line-less entries.
97
firstlines = [l.replace('# 0 ', '# 1 ') for l in firstlines]
98
for expected in firstlines:
99
line = next(lines)
100
if line != expected:
101
raise NotImplementedError((line, expected))
102
103
# Do all the CLI-provided includes.
104
filter_reqfile = (lambda f: _filter_reqfile(f, reqfile, samefiles))
105
make_info = (lambda lno: _common.FileInfo(reqfile, lno))
106
last = None
107
for line in lines:
108
assert last != reqfile, (last,)
109
lno, included, flags = _parse_marker_line(line, reqfile)
110
if not included:
111
raise NotImplementedError((line,))
112
if included == reqfile:
113
# This will be the last one.
114
assert not flags, (line, flags)
115
else:
116
assert 1 in flags, (line, flags)
117
yield from _iter_top_include_lines(
118
lines,
119
_normpath(included, cwd),
120
cwd,
121
filter_reqfile,
122
make_info,
123
raw,
124
)
125
last = included
126
# The last one is always the requested file.
127
assert included == reqfile, (line,)
128
129
130
def _iter_top_include_lines(lines, topfile, cwd,
131
filter_reqfile, make_info,
132
raw):
133
partial = 0 # depth
134
files = [topfile]
135
# We start at 1 in case there are source lines (including blank onces)
136
# before the first marker line. Also, we already verified in
137
# _parse_marker_line() that the preprocessor reported lno as 1.
138
lno = 1
139
for line in lines:
140
if line == '# 0 "<command-line>" 2' or line == '# 1 "<command-line>" 2':
141
# We're done with this top-level include.
142
return
143
144
_lno, included, flags = _parse_marker_line(line)
145
if included:
146
lno = _lno
147
included = _normpath(included, cwd)
148
# We hit a marker line.
149
if 1 in flags:
150
# We're entering a file.
151
# XXX Cycles are unexpected?
152
#assert included not in files, (line, files)
153
files.append(included)
154
elif 2 in flags:
155
# We're returning to a file.
156
assert files and included in files, (line, files)
157
assert included != files[-1], (line, files)
158
while files[-1] != included:
159
files.pop()
160
# XXX How can a file return to line 1?
161
#assert lno > 1, (line, lno)
162
else:
163
if included == files[-1]:
164
# It's the next line from the file.
165
assert lno > 1, (line, lno)
166
else:
167
# We ran into a user-added #LINE directive,
168
# which we promptly ignore.
169
pass
170
elif not files:
171
raise NotImplementedError((line,))
172
elif filter_reqfile(files[-1]):
173
assert lno is not None, (line, files[-1])
174
if (m := PREPROC_DIRECTIVE_RE.match(line)):
175
name, = m.groups()
176
if name != 'pragma':
177
raise Exception(line)
178
else:
179
line = re.sub(r'__inline__', 'inline', line)
180
if not raw:
181
line, partial = _strip_directives(line, partial=partial)
182
yield _common.SourceLine(
183
make_info(lno),
184
'source',
185
line or '',
186
None,
187
)
188
lno += 1
189
190
191
def _parse_marker_line(line, reqfile=None):
192
m = LINE_MARKER_RE.match(line)
193
if not m:
194
return None, None, None
195
lno, origfile, flags = m.groups()
196
lno = int(lno)
197
assert origfile not in META_FILES, (line,)
198
assert lno > 0, (line, lno)
199
flags = set(int(f) for f in flags.split()) if flags else ()
200
201
if 1 in flags:
202
# We're entering a file.
203
assert lno == 1, (line, lno)
204
assert 2 not in flags, (line,)
205
elif 2 in flags:
206
# We're returning to a file.
207
#assert lno > 1, (line, lno)
208
pass
209
elif reqfile and origfile == reqfile:
210
# We're starting the requested file.
211
assert lno == 1, (line, lno)
212
assert not flags, (line, flags)
213
else:
214
# It's the next line from the file.
215
assert lno > 1, (line, lno)
216
return lno, origfile, flags
217
218
219
def _strip_directives(line, partial=0):
220
# We assume there are no string literals with parens in directive bodies.
221
while partial > 0:
222
if not (m := re.match(r'[^{}]*([()])', line)):
223
return None, partial
224
delim, = m.groups()
225
partial += 1 if delim == '(' else -1 # opened/closed
226
line = line[m.end():]
227
228
line = re.sub(r'__extension__', '', line)
229
line = re.sub(r'__thread\b', '_Thread_local', line)
230
231
while (m := COMPILER_DIRECTIVE_RE.match(line)):
232
before, _, _, closed = m.groups()
233
if closed:
234
line = f'{before} {line[m.end():]}'
235
else:
236
after, partial = _strip_directives(line[m.end():], 2)
237
line = f'{before} {after or ""}'
238
if partial:
239
break
240
241
return line, partial
242
243
244
def _filter_reqfile(current, reqfile, samefiles):
245
if current == reqfile:
246
return True
247
if current == '<stdin>':
248
return True
249
if current in samefiles:
250
return True
251
return False
252
253
254
def _normpath(filename, cwd):
255
assert cwd
256
return os.path.normpath(os.path.join(cwd, filename))
257
258