• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os.path
2import re
3
4from . import common as _common
5
6
7TOOL = 'gcc'
8
9# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
10LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"(?: [1234])*$')
11PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*')
12COMPILER_DIRECTIVE_RE = re.compile(r'''
13    ^
14    (.*?)  # <before>
15    (__\w+__)  # <directive>
16    \s*
17    [(] [(]
18    (
19        [^()]*
20        (?:
21            [(]
22            [^()]*
23            [)]
24            [^()]*
25         )*
26     )  # <args>
27    ( [)] [)] )?  # <closed>
28''', re.VERBOSE)
29
30POST_ARGS = (
31    '-pthread',
32    '-std=c99',
33    #'-g',
34    #'-Og',
35    #'-Wno-unused-result',
36    #'-Wsign-compare',
37    #'-Wall',
38    #'-Wextra',
39    '-E',
40)
41
42
43def preprocess(filename, incldirs=None, macros=None, samefiles=None):
44    text = _common.preprocess(
45        TOOL,
46        filename,
47        incldirs=incldirs,
48        macros=macros,
49        #preargs=PRE_ARGS,
50        postargs=POST_ARGS,
51        executable=['gcc'],
52        compiler='unix',
53    )
54    return _iter_lines(text, filename, samefiles)
55
56
57def _iter_lines(text, filename, samefiles, *, raw=False):
58    lines = iter(text.splitlines())
59
60    # Build the lines and filter out directives.
61    partial = 0  # depth
62    origfile = None
63    for line in lines:
64        m = LINE_MARKER_RE.match(line)
65        if m:
66            lno, origfile = m.groups()
67            lno = int(lno)
68        elif _filter_orig_file(origfile, filename, samefiles):
69            if (m := PREPROC_DIRECTIVE_RE.match(line)):
70                name, = m.groups()
71                if name != 'pragma':
72                    raise Exception(line)
73            else:
74                if not raw:
75                    line, partial = _strip_directives(line, partial=partial)
76                yield _common.SourceLine(
77                    _common.FileInfo(filename, lno),
78                    'source',
79                    line or '',
80                    None,
81                )
82            lno += 1
83
84
85def _strip_directives(line, partial=0):
86    # We assume there are no string literals with parens in directive bodies.
87    while partial > 0:
88        if not (m := re.match(r'[^{}]*([()])', line)):
89            return None, partial
90        delim, = m.groups()
91        partial += 1 if delim == '(' else -1  # opened/closed
92        line = line[m.end():]
93
94    line = re.sub(r'__extension__', '', line)
95
96    while (m := COMPILER_DIRECTIVE_RE.match(line)):
97        before, _, _, closed = m.groups()
98        if closed:
99            line = f'{before} {line[m.end():]}'
100        else:
101            after, partial = _strip_directives(line[m.end():], 2)
102            line = f'{before} {after or ""}'
103            if partial:
104                break
105
106    return line, partial
107
108
109def _filter_orig_file(origfile, current, samefiles):
110    if origfile == current:
111        return True
112    if origfile == '<stdin>':
113        return True
114    if os.path.isabs(origfile):
115        return False
116
117    for filename in samefiles or ():
118        if filename.endswith(os.path.sep):
119            filename += os.path.basename(current)
120        if origfile == filename:
121            return True
122
123    return False
124