Completed
Push — master ( fdb94b...427a48 )
by srz
01:39
created

check_pp()   F

Complexity

Conditions 18

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 18
dl 0
loc 34
rs 2.7087

How to fix   Complexity   

Complexity

Complex classes like check_pp() 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_PRAGMA_GCC_WARN_PUSH'
155
				, 'IUTEST_PRAGMA_GCC_WARN_DISABLE'
156
				, 'IUTEST_PRAGMA_GCC_WARN_POP'
157
				, 'IUTEST_ATTRIBUTE_UNUSED_'
158
				, 'IUTEST_ATTRIBUTE_DEPRECATED_'
159
				, 'IUTEST_ATTRIBUTE_PURE_'
160
				, 'IUTEST_ATTRIBUTE_NORETURN_'
161
				]
162
163
# 
164
clang_has_features = { 'cxx_nullptr':'1'
165
				, 'cxx_attributes':'1'
166
				, 'cxx_auto_type':'1' 
167
				, 'cxx_constexpr':'1'
168
				, 'cxx_decltype':'1'
169
				, 'cxx_defaulted_functions':'1'
170
				, 'cxx_deleted_functions':'1'
171
				, 'cxx_explicit_conversions':'1'
172
				, 'cxx_generalized_initializers':'1'
173
				, 'cxx_lambdas':'1'
174
				, 'cxx_noexcept':'1'
175
				, 'cxx_override_control':'1'
176
				, 'cxx_rtti':'1'
177
				, 'cxx_rvalue_references':'1'
178
				, 'cxx_static_assert':'1'
179
				, 'cxx_strong_enums':'1'
180
				, 'cxx_unicode_literals':'1'
181
				, 'cxx_variadic_templates':'1'
182
				, 'c_generic_selections': '0'
183
				}
184
185
clang_has_include = { '<cxxabi.h>':'1'
186
					, '<uchar.h>':'1'
187
					, '<experimental/any>':'0'
188
					, '<ext/cmath>':'0'
189
					, '<array>':'1'
190
					, '<future>':'1'
191
					, '<ratio>':'1'
192
					, '<shared_mutex>':'1'
193
					, '<scoped_allocator>':'1'
194
					, '<typeindex>':'1'
195
					, '<type_traits>':'1'
196
					, '<tr1/tuple>':'0'
197
					}
198
199
#
200
#
201
def expand_macro(line, macros):
202
	dst = ""
203
	for s in re.split('([\(\):;{} /%+\-=<>!&\|*#]+)', line):
204
		if s in expands_macros and s in macros:
205
			if macros[s]:
206
				dst += macros[s]
207
		else:
208
			dst += s
209
	return dst
210
211
#
212
# 
213
def append_define(line, depth, macros, unkowns):
214
	def append(d, v, depth, macros, unkowns):
215
		d = re.sub('\(.*\)', '', d)
216
		if any(x == -1 for x in depth):
217
			unkowns.append(d)
218
		else:
219
			macros[d] = v
220
		return d
221
	m = re.match("#\s*define (\S+)\s(.+)$", line)
222
	if m:
223
		return append(m.group(1), m.group(2), depth, macros, unkowns)
224
	else:
225
		m = re.match("#\s*define (\S+)$", line)
226
		if m:
227
			return append(m.group(1), None, depth, macros, unkowns)
228
	return None
229
230
#
231
#
232
def expand_ppif_macro(expr, macros, unkowns):
233
	expand = ""
234
	for s in re.split('(&&|\|\||!)', expr):
235
		if s == '&&':
236
			expand += ' and '
237
		elif s == '||':
238
			expand += ' or '
239
		elif s == '!':
240
			expand += " not "
241
		else:
242
			m = re.match("(.*)defined\((.*?)\)(.*)", s)
243
			if m:
244
				d = m.group(2)
245
				if d in unkowns:
246
					expand += s
247
				else:
248
					f = d in macros
249
					expand += m.group(1) + str(f) + m.group(3)
250
				continue
251
			m = re.match("(.*)__has_include\((.*?)\)(.*)", s)
252
			if m:
253
				f = m.group(2)
254
				if f in clang_has_include:
255
					expand += m.group(1) + clang_has_include[f] + m.group(3)
256
				else:
257
					expand += s
258
				continue
259
			m = re.match("(.*)__has_feature\((.*?)\)(.*)", s)
260
			if m:
261
				f = m.group(2)
262
				if f in clang_has_features:
263
					expand += m.group(1) + clang_has_features[f] + m.group(3)
264
					continue
265
			for w in re.split('([+\-=<>\(\)]+)', s):
266
				if re.match('[+\-=<>\(\)]+', w) or w.isspace():
267
					expand += w
268
				elif len(w) > 0:
269
					if w in unkowns:
270
						expand += s
271
					elif w in macros:
272
						expand += expand_ppif_macro(macros[w], macros, unkowns)
273
					elif w.isdigit():
274
						expand += w
275
					else:
276
						expand += '0'
277
278
	expand = expand.replace('0(0)', '0')
279
	expand = expand.replace('not =', '!=')
280
	return expand
281
282
#
283
#
284
def eval_ppif(expr, macros, unkowns):
285
	expand = expand_ppif_macro(expr, macros, unkowns)
286
	try:
287
		if eval(expand):
288
			return 1
289
		else:
290
			return 0
291
	except Exception, e:
292
		if not any( x in expand for x in unkowns ):
293
			if True:
294
				print(expr)
295
				print(expand)
296
				print(e)
297
		return -1
298
						
299
#
300
#
301
def check_ppif(ins, expr, macros, unkowns):
302
	if ins == "if" or ins == "elif":
303
		return eval_ppif(expr, macros, unkowns)
304
	elif ins == "ifdef":
305
		if expr in unkowns:
306
			return -1
307
		if expr not in macros:
308
			return 0
309
	elif ins == "ifndef":
310
		if expr in unkowns:
311
			return -1
312
		if expr in macros:
313
			return 0
314
	return 1
315
316
#
317
#
318
def check_pp(line, depth, brothers, macros, unkowns):
319
	m = re.match("#\s*(ifdef|ifndef|if)\s*(.*)$", line)
320
	if m:
321
		f = check_ppif(m.group(1), m.group(2), macros, unkowns)
322
		depth.append(f)
323
		brothers.append([])
324
		return all( x != 0 for x in depth ) and f == -1
325
	m = re.match("#\s*elif(.*)$", line)
326
	if m:
327
		brother = brothers[-1]
328
		brother.append(depth[-1])
329
		f = 0
330
		if not any( x == 1 for x in brother):
331
			f = check_ppif("elif", m.group(1), macros, unkowns)
332
		depth[-1] = f
333
		return all( x != 0 for x in depth ) and any(x == -1 for x in brother)
334
	m = re.match("#\s*else\s*$", line)
335
	if m:
336
		brother = brothers[-1]
337
		f = depth[-1]
338
		if f == 1 or any(x == 1 for x in brother):
339
			f = 0
340
		elif f == 0:
341
			f = 1
342
		depth[-1] = f
343
		return all( x != 0 for x in depth ) and f == -1
344
	if re.match("#\s*endif", line):
345
		brother = brothers[-1]
346
		f = depth.pop()
347
		b1 = all( x != 0 for x in depth )
348
		b2 = any(x == -1 for x in brother)
349
		brothers.pop()
350
		return b1 and (f == -1 or b2)
351
	return len(depth) == 0 or all( x != 0 for x in depth )
352
353
#
354
#
355
def reduction(line):
356
	line = line.replace('IIUT_', 'II_')
357
	line = line.replace('II_PP_', 'IP_')
358
	line = re.sub('\s+', ' ', line)
359
	line = re.sub('\s$', '', line)
360
	return line
361
#
362
#
363
def preprocess(code, macros):
364
	macros = predefined_macros
365
	unkowns = []
366
	depth = []
367
	brother = []
368
	dst = ""
369
	for line in code.splitlines():
370
		# c++ comment
371
		if re.match('^//.*', line):
372
			continue
373
		# if/ifdef/ifndef/elif/endif
374
		if check_pp(line, depth, brother, macros, unkowns):
375
			# define
376
			d = append_define(line, depth, macros, unkowns)
377
			if d:
378
				if d in expands_macros:
379
					continue
380
			line = expand_macro(line, macros)
381
			if len(line) > 0:
382
				line = reduction(line)
383
				dst += line + "\n"
384
		
385
	#for k,v in sorted(macros.items()):
386
	#	if v == None:
387
	#		print(k)
388
	#	else:
389
	#		print(k + " " + v)
390
	return dst
391
392
#
393
#
394
def run_impl(code, options):
395
	r = run_paiza(code, options)
396
	b = show_result(r)
397
	sys.exit(b)
398
399
#
400
# run
401
def run(options):
402
	filepath = options.code
403
	if not os.path.exists(filepath):
404
		sys.exit(1)
405
	code = make_code(filepath, options.encoding, options.expand_include)
406
	if options.output:
407
		output_code(options.output, code, options.encoding)
408
	try:
409
		run_impl(code, options)
410
	except paizaio.TooLongException, e:
411
		print(e)
412
		output = options.output
413
		if not options.output:
414
			output = "paizaio-toolong-sourcecode.cpp"
415
			output_code(output, code, options.encoding)
416
			print("source code -> " + output)
417
		try:
418
			output = os.path.basename(filepath) + ".p"
419
			macros = { '__clnag__':'1', '_LIBCPP_VERSION':'1101', 'NULL':'0' }
420
			code = preprocess(code, macros)
421
			output_code("paizaio-sourcecode.cpp", code, options.encoding)
422
			run_impl(code, options)
423
		except paizaio.TooLongException, e:
424
			print(e)
425
			sys.exit(1)
426
		except Exception, e:
427
			print(e)
428
			raise
429
	except:
430
		raise
431
432
#
433
#
434
def main():
435
	options = parse_command_line()
436
	run(options)
437
438
if __name__ == '__main__':
439
	main()
440