Completed
Push — master ( d5fdbe...1b7e63 )
by srz
08:59 queued 04:24
created

PaizaPreprocessor.__expand_function_macro()   F

Complexity

Conditions 12

Size

Total Lines 29
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 27
nop 2
dl 0
loc 29
rs 2.7855
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like iupaizaio_pp.PaizaPreprocessor.__expand_function_macro() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
#
3
# iupaizaio_pp.py
4
#
5
# Copyright (C) 2015-2016, Takazumi Shirayanagi
6
# This software is released under the new BSD License,
7
# see LICENSE
8
#
9
10
import re
11
12
predefined_macros = {
13
    '__clang__': '1',
14
    '_LIBCPP_VERSION': '1101',
15
    'NULL': '0',
16
    '__linux__': '1',
17
    '__cplusplus': '201402',
18
    '__cpp_rvalue_references': '200610',
19
    '__has_include': None
20
}
21
22
#userdefined_macros = { '': '1'
23
#}
24
25
expands_macros = [
26
    'IUTEST_IPP_INLINE',
27
    'IUTEST_NULLPTR',
28
    'IUTEST_CXX_CONSTEXPR',
29
    'IUTEST_CXX_CONSTEXPR_OR_CONST',
30
    'IUTEST_CXX_DELETED_FUNCTION',
31
    'IUTEST_CXX_DEFAULT_FUNCTION',
32
    'IUTEST_CXX_EXPLICIT_CONVERSION',
33
    'IUTEST_CXX_NOEXCEPT_SPEC',
34
    'IUTEST_CXX_OVERRIDE',
35
    'IUTEST_CXX_FINAL',
36
    'IUTEST_CXX_NOTHROW',
37
    'IUTEST_PRAGMA_GCC_WARN_PUSH',
38
    'IUTEST_PRAGMA_GCC_WARN_DISABLE',
39
    'IUTEST_PRAGMA_GCC_WARN_POP',
40
    'IUTEST_ATTRIBUTE_UNUSED_',
41
    'IUTEST_ATTRIBUTE_DEPRECATED_',
42
    'IUTEST_ATTRIBUTE_PURE_',
43
    'IUTEST_ATTRIBUTE_NORETURN_'
44
]
45
46
#
47
expand_function_macros = [
48
    'IUTEST_PRAGMA_CRT_SECURE_WARN_DISABLE_BEGIN',
49
    'IUTEST_PRAGMA_CRT_SECURE_WARN_DISABLE_END',
50
    'IUTEST_PRAGMA_EXTERN_TEMPLATE_WARN_DISABLE_BEGIN',
51
    'IUTEST_PRAGMA_EXTERN_TEMPLATE_WARN_DISABLE_END',
52
    'IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_BEGIN',
53
    'IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_END',
54
    'IUTEST_PRAGMA_MSC_WARN_PUSH',
55
    'IUTEST_PRAGMA_MSC_WARN_DISABLE',
56
    'IUTEST_PRAGMA_MSC_WARN_POP',
57
    'IUTEST_WORKAROUND_MSC_STLSTREAM_C4250',
58
    'IUTEST_EXPLICIT_TEMPLATE_TYPE_',
59
    'IUTEST_APPEND_EXPLICIT_TEMPLATE_TYPE_'
60
]
61
62
#
63
clang_has_features = {
64
    'cxx_nullptr': '1',
65
    'cxx_attributes': '1',
66
    'cxx_auto_type': '1',
67
    'cxx_constexpr': '1',
68
    'cxx_decltype': '1',
69
    'cxx_defaulted_functions': '1',
70
    'cxx_deleted_functions': '1',
71
    'cxx_explicit_conversions': '1',
72
    'cxx_generalized_initializers': '1',
73
    'cxx_lambdas': '1',
74
    'cxx_noexcept': '1',
75
    'cxx_override_control': '1',
76
    'cxx_rtti': '1',
77
    'cxx_rvalue_references': '1',
78
    'cxx_static_assert': '1',
79
    'cxx_strong_enums': '1',
80
    'cxx_unicode_literals': '1',
81
    'cxx_variadic_templates': '1',
82
    'c_generic_selections': '0'
83
}
84
85
clang_has_include = {
86
    '<cxxabi.h>': '1',
87
    '<cuchar>': '0',
88
    '<uchar.h>': '0',
89
    '<experimental/any>': '0',
90
    '<ext/cmath>': '0',
91
    '<array>': '1',
92
    '<future>': '1',
93
    '<ratio>': '1',
94
    '<shared_mutex>': '1',
95
    '<scoped_allocator>': '1',
96
    '<typeindex>': '1',
97
    '<type_traits>': '1',
98
    '<tr1/tuple>': '0'
99
}
100
101
RE_MACRO_SPLIT = re.compile('([\(\):;{} /%+\-=<>!&\|*#]+)')
102
RE_SPLIT_PAREN = re.compile('([\(\)])')
103
RE_FUNC_MACRO = re.compile('([\w_]+)\((.*?)\)')
104
RE_DEFINE = re.compile('#\s*define (\S+)\s*(.*)$')
105
RE_DEFINE_PARSE = re.compile('(.*)defined\((.*?)\)(.*)')
106
RE_HAS_INCLUDE = re.compile('(.*)__has_include\((.*?)\)(.*)')
107
RE_HAS_FEATURE = re.compile('(.*)__has_feature\((.*?)\)(.*)')
108
RE_SPLIT_OP = re.compile('(&&|\|\||!)')
109
RE_SYMBOLMARK = re.compile('([+\-=<>\(\)]+)')
110
RE_PPIF = re.compile('#\s*(ifdef|ifndef|if)\s*(.*)$')
111
RE_PPELIF = re.compile('#\s*elif(.*)$')
112
RE_PPELSE = re.compile('#\s*else\s*$')
113
RE_PPENDIF = re.compile('#\s*endif')
114
RE_CPP_COMMENT = re.compile('^//.*')
115
116
117
class PaizaPreprocessor:
118
    macros = predefined_macros
119
    unkowns = []
120
    depth = []
121
    brothers = []
122
123
    def __expand_macro(self, line):
124
        dst = ""
125
        for s in RE_MACRO_SPLIT.split(line):
126
            if s in expands_macros and s in self.macros:
127
                if self.macros[s]:
128
                    dst += self.macros[s]
129
            else:
130
                dst += s
131
        return self.__expand_function_macro(dst)
132
133
    def __expand_function_macro(self, line):
134
        dst = ""
135
        tokens = []
136
        prev = ""
137
        for s in RE_SPLIT_PAREN.split(line):
138
            if s == '(':
139
                tokens.append(prev)
140
            elif s == ')' and len(tokens) > 0:
141
                tokens[-1] += prev + s
142
                s = ""
143
                ss = tokens.pop()
144
                for m in RE_FUNC_MACRO.finditer(ss):
145
                    d = m.group(1)
146
                    if d in expand_function_macros:
147
                        if d in self.macros and self.macros[d] is None:
148
                            ss = ss.replace(m.group(0), '')
149
                if len(tokens) > 0:
150
                    tokens[-1] += ss
151
                else:
152
                    dst += ss
153
            elif len(tokens) > 0:
154
                tokens[-1] += prev
155
            else:
156
                dst += prev
157
            prev = s
158
        for s in tokens:
159
            dst += s
160
        dst += prev
161
        return dst
162
163
    def __append_define(self, line):
164
        def append(d, v, depth, macros, unkowns):
165
            d = re.sub('\(.*\)', '', d)
166
            if any(x == -1 for x in depth):
167
                unkowns.append(d)
168
            else:
169
                if len(v) == 0:
170
                    macros[d] = None
171
                else:
172
                    macros[d] = v
173
            return d
174
        m = RE_DEFINE.match(line)
175
        if m:
176
            return append(m.group(1), m.group(2), self.depth, self.macros, self.unkowns)
177
        return None
178
179
    def __expand_ppif_macro(self, expr):
180
        expand = ""
181
        for s in RE_SPLIT_OP.split(expr):
182
            if s == '&&':
183
                expand += ' and '
184
            elif s == '||':
185
                expand += ' or '
186
            elif s == '!':
187
                expand += " not "
188
            else:
189
                m = RE_DEFINE_PARSE.match(s)
190
                if m:
191
                    d = m.group(2)
192
                    if d in self.unkowns:
193
                        expand += s
194
                    else:
195
                        f = d in self.macros
196
                        expand += m.group(1) + str(f) + m.group(3)
197
                    continue
198
                m = RE_HAS_INCLUDE.match(s)
199
                if m:
200
                    f = m.group(2)
201
                    if f in clang_has_include:
202
                        expand += m.group(1) + clang_has_include[f] + m.group(3)
203
                    else:
204
                        expand += s
205
                    continue
206
                m = RE_HAS_FEATURE.match(s)
207
                if m:
208
                    f = m.group(2)
209
                    if f in clang_has_features:
210
                        expand += m.group(1) + clang_has_features[f] + m.group(3)
211
                        continue
212
                for w in RE_SYMBOLMARK.split(s):
213
                    if RE_SYMBOLMARK.match(w) or w.isspace():
214
                        expand += w
215
                    elif len(w) > 0:
216
                        if w in self.unkowns:
217
                            expand += s
218
                        elif w in self.macros:
219
                            expand += self.__expand_ppif_macro(self.macros[w])
220
                        elif w.isdigit():
221
                            expand += w
222
                        else:
223
                            expand += '0'
224
225
        expand = expand.replace('0(0)', '0')
226
        expand = expand.replace('not =', '!=')
227
        return expand
228
229
    def __eval_ppif(self, expr):
230
        expand = self.__expand_ppif_macro(expr)
231
        try:
232
            if eval(expand):
233
                return 1
234
            else:
235
                return 0
236
        except Exception as e:
237
            if not any(x in expand for x in self.unkowns):
238
                if True:
239
                    print(expr)
240
                    print(expand)
241
                    print(e)
242
            return -1
243
244
    def __check_ppif(self, ins, expr):
245
        if ins == "if" or ins == "elif":
246
            return self.__eval_ppif(expr)
247
        elif ins == "ifdef":
248
            if expr in self.unkowns:
249
                return -1
250
            if expr not in self.macros:
251
                return 0
252
        elif ins == "ifndef":
253
            if expr in self.unkowns:
254
                return -1
255
            if expr in self.macros:
256
                return 0
257
        return 1
258
259
    def __check_pp(self, line):
260
        m = RE_PPIF.match(line)
261
        if m:
262
            f = self.__check_ppif(m.group(1), m.group(2))
263
            self.depth.append(f)
264
            self.brothers.append([])
265
            return all(x != 0 for x in self.depth) and f == -1
266
        m = RE_PPELIF.match(line)
267
        if m:
268
            brother = self.brothers[-1]
269
            brother.append(self.depth[-1])
270
            f = 0
271
            if not any(x == 1 for x in brother):
272
                f = self.__check_ppif("elif", m.group(1))
273
            self.depth[-1] = f
274
            return all(x != 0 for x in self.depth) and any(x == -1 for x in brother)
275
        m = RE_PPELSE.match(line)
276
        if m:
277
            brother = self.brothers[-1]
278
            f = self.depth[-1]
279
            if f == 1 or any(x == 1 for x in brother):
280
                f = 0
281
            elif f == 0:
282
                f = 1
283
            self.depth[-1] = f
284
            return all(x != 0 for x in self.depth) and f == -1
285
        if RE_PPENDIF.match(line):
286
            brother = self.brothers[-1]
287
            f = self.depth.pop()
288
            b1 = all(x != 0 for x in self.depth)
289
            b2 = any(x == -1 for x in brother)
290
            self.brothers.pop()
291
            return b1 and (f == -1 or b2)
292
        return len(self.depth) == 0 or all(x != 0 for x in self.depth)
293
294
    def __reduction(self, line):
295
        line = line.replace('IIUT_', 'II_')
296
        line = line.replace('II_PP_', 'IP_')
297
        line = line.replace('IUTEST_UNUSED_VAR', '(void)')
298
        line = re.sub('\s+', ' ', line)
299
        line = re.sub('\s$', '', line)
300
        return line
301
302
    def preprocess(self, code, add_macros):
303
        self.macros = dict(self.macros.items() + add_macros.items())
304
        dst = ""
305
        for line in code.splitlines():
306
            # c++ comment
307
            if RE_CPP_COMMENT.match(line):
308
                continue
309
            # if/ifdef/ifndef/elif/endif
310
            if self.__check_pp(line):
311
                # define
312
                d = self.__append_define(line)
313
                if d:
314
                    if d in expands_macros or d in expand_function_macros:
315
                        continue
316
                    if d in ['IUTEST_UNUSED_VAR']:
317
                        continue
318
                line = self.__expand_macro(line)
319
                if len(line) > 0:
320
                    line = self.__reduction(line)
321
                    dst += line + "\n"
322
        return dst
323