Completed
Push — master ( 427a48...4d207a )
by srz
01:20
created

expand_function_macro()   F

Complexity

Conditions 12

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 12
dl 0
loc 29
rs 2.7855

How to fix   Complexity   

Complexity

Complex classes like 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
# iupaiza.py
4
#
5
6
import os
7
import sys
8
import re
9
import codecs
10
import argparse
11
import paizaio
12
13
from argparse import ArgumentParser
14
from paizaio import PaizaIO
15
16
IUTEST_FUSED_SRC = os.path.join(os.path.dirname(__file__), '../../fused-src/iutest.min.hpp')
17
IUTEST_INCLUDE_REGEX = re.compile(r'^\s*#\s*include\s*".*iutest\.hpp"')
18
EXPAND_INCLUDE_REGEX = re.compile(r'^\s*#\s*include\s*"(.*?)"')
19
20
#
21
# command line option
22
def parse_command_line():
23
	parser = ArgumentParser()
24
	parser.add_argument(
25
		'-v'
26
		, '--version'
27
		, action='version'
28
		, version=u'%(prog)s version 0.1'
29
	)
30
	parser.add_argument(
31
		'--stdin'
32
		, help = 'set stdin.'
33
	)
34
	parser.add_argument(
35
		'-o'
36
		, '--output'
37
		, help = 'output source code.'
38
	)
39
	parser.add_argument(
40
		  '--encoding'
41
		, help = 'set encoding.'
42
	)
43
	parser.add_argument(
44
		  '--expand_include'
45
		, action='store_true'
46
		, help = 'expand include file.'
47
	)
48
	parser.add_argument(
49
		'code'
50
		, metavar='CODE'
51
		, help = 'source code file'
52
	)
53
	options, unknown = parser.parse_known_args()
54
	return options
55
56
#
57
# file open
58
def file_open(path, mode, encoding):
59
	if encoding:
60
		file = codecs.open(path, mode, encoding)
61
	else:
62
		file = open(path, mode)
63
	return file
64
65
#
66
# make code
67 View Code Duplication
def make_code(path, encoding, expand):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
68
	code = ''
69
	file = file_open(path, 'r', encoding)
70
	for line in file:
71
		m = IUTEST_INCLUDE_REGEX.match(line)
72
		if m:
73
			f = codecs.open(IUTEST_FUSED_SRC, 'r', 'utf-8-sig')
74
			
75
			code += '//==================================================================>>>> ' + line
76
			code += f.read()
77
			code += '//==================================================================<<<< ' + line
78
		else:
79
			if expand:
80
				m = EXPAND_INCLUDE_REGEX.match(line)
81
				if m:
82
					include_path = os.path.join(os.path.dirname(path), m.group(1))
83
					if os.path.exists(include_path):
84
						code += make_code(include_path, encoding, expand)
85
						code += '// '
86
			code += line
87
	file.close()
88
	return code
89
90
#
91
# run paiza
92
def run_paiza(code, options):
93
	paiza = PaizaIO()
94
	paiza.longpoll(True)
95
	paiza.longpoll_timeout(100)
96
	paiza.code(code)
97
	return paiza.run()
98
99
#
100
# show result
101
def show_result(r):
102
	if 'error' in r:
103
		print(r['error'])
104
		sys.exit(1)
105
	build_result = r['build_result']
106
	if 'success' in build_result:
107
		if 'stdout' in r:
108
			print('stdout:')
109
			print(r['stdout'])
110
		if 'stderr' in r:
111
			print('stderr:')
112
			print(r['stderr'])
113
		if 'time' in r:
114
			print('time:')
115
			print(r['time'])
116
		if 'memory' in r:
117
			print('memory:')
118
			print(r['memory'])
119
		if 'exit_code' in r:
120
			return int(r['exit_code'])
121
	else:
122
		if 'build_stderr' in r:
123
			print(r['build_stderr'])
124
		if 'build_exit_code' in r:
125
			return int(r['build_exit_code'])
126
127
	return 1
128
129
#
130
# output code
131
def output_code(path, code, encoding):
132
	f = file_open(path, 'w', encoding)
133
	f.write(code)
134
	f.close()
135
136
predefined_macros = { '__clang__':'1'
137
					, '_LIBCPP_VERSION':'1101'
138
					, 'NULL':'0'
139
					, '__linux__':'1'
140
					, '__cplusplus':'201402'
141
					, '__cpp_rvalue_references':'200610'
142
					, '__has_include':None
143
					}
144
#userdefined_macros = { '':'1'
145
#					}
146
expands_macros = [ 'IUTEST_IPP_INLINE'
147
				, 'IUTEST_NULLPTR'
148
				, 'IUTEST_CXX_CONSTEXPR'
149
				, 'IUTEST_CXX_CONSTEXPR_OR_CONST'
150
				, 'IUTEST_CXX_DELETED_FUNCTION'
151
				, 'IUTEST_CXX_DEFAULT_FUNCTION'
152
				, 'IUTEST_CXX_EXPLICIT_CONVERSION'
153
				, 'IUTEST_CXX_NOEXCEPT_SPEC'
154
				, 'IUTEST_CXX_OVERRIDE'
155
				, 'IUTEST_CXX_FINAL'
156
				, 'IUTEST_CXX_NOTHROW'
157
				, 'IUTEST_PRAGMA_GCC_WARN_PUSH'
158
				, 'IUTEST_PRAGMA_GCC_WARN_DISABLE'
159
				, 'IUTEST_PRAGMA_GCC_WARN_POP'
160
				, 'IUTEST_ATTRIBUTE_UNUSED_'
161
				, 'IUTEST_ATTRIBUTE_DEPRECATED_'
162
				, 'IUTEST_ATTRIBUTE_PURE_'
163
				, 'IUTEST_ATTRIBUTE_NORETURN_'
164
				]
165
166
#
167
expand_function_macros = [ 'IUTEST_PRAGMA_CRT_SECURE_WARN_DISABLE_BEGIN'
168
						, 'IUTEST_PRAGMA_CRT_SECURE_WARN_DISABLE_END'
169
						, 'IUTEST_PRAGMA_EXTERN_TEMPLATE_WARN_DISABLE_BEGIN'
170
						, 'IUTEST_PRAGMA_EXTERN_TEMPLATE_WARN_DISABLE_END'
171
						, 'IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_BEGIN'
172
						, 'IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_END'
173
						, 'IUTEST_PRAGMA_MSC_WARN_PUSH'
174
						, 'IUTEST_PRAGMA_MSC_WARN_DISABLE'
175
						, 'IUTEST_PRAGMA_MSC_WARN_POP'
176
						, 'IUTEST_WORKAROUND_MSC_STLSTREAM_C4250'
177
						, 'IUTEST_EXPLICIT_TEMPLATE_TYPE_'
178
						, 'IUTEST_APPEND_EXPLICIT_TEMPLATE_TYPE_'
179
						]
180
181
# 
182
clang_has_features = { 'cxx_nullptr':'1'
183
				, 'cxx_attributes':'1'
184
				, 'cxx_auto_type':'1' 
185
				, 'cxx_constexpr':'1'
186
				, 'cxx_decltype':'1'
187
				, 'cxx_defaulted_functions':'1'
188
				, 'cxx_deleted_functions':'1'
189
				, 'cxx_explicit_conversions':'1'
190
				, 'cxx_generalized_initializers':'1'
191
				, 'cxx_lambdas':'1'
192
				, 'cxx_noexcept':'1'
193
				, 'cxx_override_control':'1'
194
				, 'cxx_rtti':'1'
195
				, 'cxx_rvalue_references':'1'
196
				, 'cxx_static_assert':'1'
197
				, 'cxx_strong_enums':'1'
198
				, 'cxx_unicode_literals':'1'
199
				, 'cxx_variadic_templates':'1'
200
				, 'c_generic_selections': '0'
201
				}
202
203
clang_has_include = { '<cxxabi.h>':'1'
204
#					, '<uchar.h>':'1'
205
					, '<uchar.h>':'0'
206
					, '<experimental/any>':'0'
207
					, '<ext/cmath>':'0'
208
					, '<array>':'1'
209
					, '<future>':'1'
210
					, '<ratio>':'1'
211
					, '<shared_mutex>':'1'
212
					, '<scoped_allocator>':'1'
213
					, '<typeindex>':'1'
214
					, '<type_traits>':'1'
215
					, '<tr1/tuple>':'0'
216
					}
217
218
RE_MACRO_SPLIT = re.compile('([\(\):;{} /%+\-=<>!&\|*#]+)')
219
def expand_macro(line, macros):
220
	dst = ""
221
	for s in RE_MACRO_SPLIT.split(line):
222
		if s in expands_macros and s in macros:
223
			if macros[s]:
224
				dst += macros[s]
225
		else:
226
			dst += s
227
	return expand_function_macro(dst, macros)
228
229
RE_SPLIT_PAREN = re.compile('([\(\)])')
230
RE_FUNC_MACRO = re.compile('([\w_]+)\((.*?)\)')
231
def expand_function_macro(line, macros):
232
	dst = ""
233
	tokens = []
234
	prev = ""
235
	for s in RE_SPLIT_PAREN.split(line):
236
		if s == '(':
237
			tokens.append(prev)
238
		elif s == ')' and len(tokens) > 0:
239
			tokens[-1] += prev + s
240
			s = ""
241
			ss = tokens.pop()
242
			for m in RE_FUNC_MACRO.finditer(ss):
243
				d = m.group(1)
244
				if d in expand_function_macros:
245
					if d in macros and macros[d] == None:
246
						ss = ss.replace(m.group(0), '')
247
			if len(tokens) > 0:
248
				tokens[-1] += ss
249
			else:
250
				dst += ss
251
		elif len(tokens) > 0:
252
			tokens[-1] += prev
253
		else:
254
			dst += prev
255
		prev = s
256
	for s in tokens:
257
		dst += s
258
	dst += prev
259
	return dst
260
261
RE_DEFINE = re.compile('#\s*define (\S+)\s*(.*)$')
262
def append_define(line, depth, macros, unkowns):
263
	def append(d, v, depth, macros, unkowns):
264
		d = re.sub('\(.*\)', '', d)
265
		if any(x == -1 for x in depth):
266
			unkowns.append(d)
267
		else:
268
			if len(v) == 0:
269
				macros[d] = None
270
			else:
271
				macros[d] = v
272
		return d
273
	m = RE_DEFINE.match(line)
274
	if m:
275
		return append(m.group(1), m.group(2), depth, macros, unkowns)
276
	return None
277
278
RE_DEFINE_PARSE = re.compile('(.*)defined\((.*?)\)(.*)')
279
RE_HAS_INCLUDE = re.compile('(.*)__has_include\((.*?)\)(.*)')
280
RE_HAS_FEATURE = re.compile('(.*)__has_feature\((.*?)\)(.*)')
281
RE_SPLIT_OP = re.compile('(&&|\|\||!)')
282
RE_SYMBOLMARK = re.compile('([+\-=<>\(\)]+)')
283
def expand_ppif_macro(expr, macros, unkowns):
284
	expand = ""
285
	for s in RE_SPLIT_OP.split(expr):
286
		if s == '&&':
287
			expand += ' and '
288
		elif s == '||':
289
			expand += ' or '
290
		elif s == '!':
291
			expand += " not "
292
		else:
293
			m = RE_DEFINE_PARSE.match(s)
294
			if m:
295
				d = m.group(2)
296
				if d in unkowns:
297
					expand += s
298
				else:
299
					f = d in macros
300
					expand += m.group(1) + str(f) + m.group(3)
301
				continue
302
			m = RE_HAS_INCLUDE.match(s)
303
			if m:
304
				f = m.group(2)
305
				if f in clang_has_include:
306
					expand += m.group(1) + clang_has_include[f] + m.group(3)
307
				else:
308
					expand += s
309
				continue
310
			m = RE_HAS_FEATURE.match(s)
311
			if m:
312
				f = m.group(2)
313
				if f in clang_has_features:
314
					expand += m.group(1) + clang_has_features[f] + m.group(3)
315
					continue
316
			for w in RE_SYMBOLMARK.split(s):
317
				if RE_SYMBOLMARK.match(w) or w.isspace():
318
					expand += w
319
				elif len(w) > 0:
320
					if w in unkowns:
321
						expand += s
322
					elif w in macros:
323
						expand += expand_ppif_macro(macros[w], macros, unkowns)
324
					elif w.isdigit():
325
						expand += w
326
					else:
327
						expand += '0'
328
329
	expand = expand.replace('0(0)', '0')
330
	expand = expand.replace('not =', '!=')
331
	return expand
332
333
#
334
#
335
def eval_ppif(expr, macros, unkowns):
336
	expand = expand_ppif_macro(expr, macros, unkowns)
337
	try:
338
		if eval(expand):
339
			return 1
340
		else:
341
			return 0
342
	except Exception, e:
343
		if not any( x in expand for x in unkowns ):
344
			if True:
345
				print(expr)
346
				print(expand)
347
				print(e)
348
		return -1
349
						
350
#
351
#
352
def check_ppif(ins, expr, macros, unkowns):
353
	if ins == "if" or ins == "elif":
354
		return eval_ppif(expr, macros, unkowns)
355
	elif ins == "ifdef":
356
		if expr in unkowns:
357
			return -1
358
		if expr not in macros:
359
			return 0
360
	elif ins == "ifndef":
361
		if expr in unkowns:
362
			return -1
363
		if expr in macros:
364
			return 0
365
	return 1
366
367
RE_PPIF = re.compile('#\s*(ifdef|ifndef|if)\s*(.*)$')
368
RE_PPELIF = re.compile('#\s*elif(.*)$')
369
RE_PPELSE = re.compile('#\s*else\s*$')
370
RE_PPENDIF = re.compile('#\s*endif')
371
def check_pp(line, depth, brothers, macros, unkowns):
372
	m = RE_PPIF.match(line)
373
	if m:
374
		f = check_ppif(m.group(1), m.group(2), macros, unkowns)
375
		depth.append(f)
376
		brothers.append([])
377
		return all( x != 0 for x in depth ) and f == -1
378
	m = RE_PPELIF.match(line)
379
	if m:
380
		brother = brothers[-1]
381
		brother.append(depth[-1])
382
		f = 0
383
		if not any( x == 1 for x in brother):
384
			f = check_ppif("elif", m.group(1), macros, unkowns)
385
		depth[-1] = f
386
		return all( x != 0 for x in depth ) and any(x == -1 for x in brother)
387
	m = RE_PPELSE.match(line)
388
	if m:
389
		brother = brothers[-1]
390
		f = depth[-1]
391
		if f == 1 or any(x == 1 for x in brother):
392
			f = 0
393
		elif f == 0:
394
			f = 1
395
		depth[-1] = f
396
		return all( x != 0 for x in depth ) and f == -1
397
	if RE_PPENDIF.match(line):
398
		brother = brothers[-1]
399
		f = depth.pop()
400
		b1 = all( x != 0 for x in depth )
401
		b2 = any(x == -1 for x in brother)
402
		brothers.pop()
403
		return b1 and (f == -1 or b2)
404
	return len(depth) == 0 or all( x != 0 for x in depth )
405
406
#
407
#
408
def reduction(line):
409
	line = line.replace('IIUT_', 'II_')
410
	line = line.replace('II_PP_', 'IP_')
411
	line = line.replace('IUTEST_UNUSED_VAR', '(void)')
412
	line = re.sub('\s+', ' ', line)
413
	line = re.sub('\s$', '', line)
414
	return line
415
416
RE_CPP_COMMENT = re.compile('^//.*')
417
def preprocess(code, macros):
418
	macros = predefined_macros
419
	unkowns = []
420
	depth = []
421
	brother = []
422
	dst = ""
423
	for line in code.splitlines():
424
		# c++ comment
425
		if RE_CPP_COMMENT.match(line):
426
			continue
427
		# if/ifdef/ifndef/elif/endif
428
		if check_pp(line, depth, brother, macros, unkowns):
429
			# define
430
			d = append_define(line, depth, macros, unkowns)
431
			if d:
432
				if d in expands_macros or d in expand_function_macros:
433
					continue
434
				if d in [ 'IUTEST_UNUSED_VAR' ]:
435
					continue
436
			line = expand_macro(line, macros)
437
			if len(line) > 0:
438
				line = reduction(line)
439
				dst += line + "\n"
440
		
441
	#for k,v in sorted(macros.items()):
442
	#	if v == None:
443
	#		print(k)
444
	#	else:
445
	#		print(k + " " + v)
446
	return dst
447
448
#
449
#
450
def run_impl(code, options):
451
	r = run_paiza(code, options)
452
	b = show_result(r)
453
	sys.exit(b)
454
455
#
456
# run
457
def run(options):
458
	filepath = options.code
459
	if not os.path.exists(filepath):
460
		sys.exit(1)
461
	code = make_code(filepath, options.encoding, options.expand_include)
462
	if options.output:
463
		output_code(options.output, code, options.encoding)
464
	try:
465
		run_impl(code, options)
466
	except paizaio.TooLongException, e:
467
		print(e)
468
		output = options.output
469
		if not options.output:
470
			output = "paizaio-toolong-sourcecode.cpp"
471
			output_code(output, code, options.encoding)
472
			print("source code -> " + output)
473
		try:
474
			output = os.path.basename(filepath) + ".p"
475
			macros = { '__clnag__':'1', '_LIBCPP_VERSION':'1101', 'NULL':'0' }
476
			code = preprocess(code, macros)
477
			output_code("paizaio-sourcecode.cpp", code, options.encoding)
478
			run_impl(code, options)
479
		except paizaio.TooLongException, e:
480
			print(e)
481
			sys.exit(1)
482
		except Exception, e:
483
			print(e)
484
			raise
485
	except:
486
		raise
487
488
#
489
#
490
def main():
491
	options = parse_command_line()
492
	run(options)
493
494
if __name__ == '__main__':
495
	main()
496