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

IutestFused.StoreMinimze()   A

Complexity

Conditions 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 14
rs 9.4285
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
# fuse_iutest_files.py
5
#
6
# Copyright (C) 2014-2016, Takazumi Shirayanagi
7
# This software is released under the new BSD License,
8
# see LICENSE
9
#
10
11
""" fuse_iutest_files.py
12
fuse_iutest_files.py [IUTEST_ROOT_DIR] OUTPUT_DIR
13
"""
14
15
import os
16
import sys
17
import re
18
import codecs
19
20
IUTEST_INCLUDE_DIR = os.path.join(os.path.dirname(__file__), '../../include')
21
22
class IutestFused:
23
	INCLUDE_REGEX = re.compile(r'^\s*#\s*include\s*"(.*iutest.*)"')
24
	IFDEF_REGEX = re.compile(r'^\s*#\s*if(def\s*\S*|\s*defined\s*\(.*\))')
25
	PREPRO_REGEX = re.compile(r'^\s*#')
26
	COMMENT_REGEX = re.compile(r'^\s*//.*')
27
	C_COMMENT_BEGIN_REGEX = re.compile(r'^\s*(.*)/\*.*')
28
	C_COMMENT_END_REGEX = re.compile(r'^\s*\*/(.*)')
29
	STRING_REGEX = re.compile(r'(".*?")')
30
	INCG_REGEX = re.compile(r'^\s*#\s*(ifndef|define|endif)[/\s]*(INCG_\S*)\s*\Z')
31
	c_comment = False
32
	store_line = ""
33
34
	def IsUnnecessaryIncludeGuard(self, line):
35
		m = self.INCG_REGEX.match(line)
36
		if m:
37
			incg = m.group(2)
38
			if incg not in { 'INCG_IRIS_IUTEST_HPP_', 'INCG_IRIS_IUTEST_SWITCH_HPP_', 'INCG_IRIS_IUTEST_SPI_HPP_' }:
39
				#print(incg)
40
				return True
41
		return False
42
43
	def StoreStrings(self, src, list):
44
		tmp = src.replace(r'\"', '$$')
45
		prev = 0
46
		dst = ""
47
		for m in self.STRING_REGEX.finditer(tmp):
48
			dst += tmp[prev:m.start()]
49
			dst += '"@STRING@"'
50
			prev = m.end()
51
			list.append(m.group(0))
52
		dst += tmp[prev:]
53
		return dst
54
55
	def RestoreStrings(self, src, list):
56
		dst = src
57
		for s in list:
58
			dst = dst.replace('"@STRING@"', s, 1)
59
		dst = dst.replace('$$', r'\"')
60
		return dst
61
62
	def StoreMinimze(self, line):
63
		store = self.store_line + line
64
		# string store
65
		str_l = []
66
		store = self.StoreStrings(store, str_l)
67
68
		store = re.sub('"@STRING@""@STRING@"', '"@STRING@"##+##"@STRING@"', store)
69
		store = re.sub('\):\s+', '):', store)
70
		
71
		# string restore
72
		store = self.RestoreStrings(store, str_l)
73
	
74
		store = store.replace('"##+##"', '')
75
		self.store_line = store
76
		
77
	def Minimze(self, line):
78
		# if defined -> ifdef
79
		line = re.sub(r'\s*#\s*if\s*defined\((\S*)\)\s*\Z', r'#ifdef \1', line)
80
		# if !defined -> ifndef
81
		line = re.sub(r'\s*#\s*if\s*!defined\((\S*)\)\s*\Z', r'#ifndef \1', line)
82
83
		# C comment
84
		if self.c_comment:
85
			m = self.C_COMMENT_END_REGEX.match(line)
86
			if m:
87
				self.c_comment = False
88
				line = m.group(1)
89
			else:
90
				return ""
91
		m = self.C_COMMENT_BEGIN_REGEX.match(line)
92
		if m:
93
			self.c_comment = True
94
			line = m.group(1)
95
		line = line.strip()
96
		if len(line) == 0:
97
			return ""
98
99
		# C++ comment
100
		if self.COMMENT_REGEX.match(line):
101
			return ""
102
		# include guard
103
		if self.IsUnnecessaryIncludeGuard(line):
104
			return ""
105
106
		# string store
107
		str_l = []
108
		line = self.StoreStrings(line, str_l)
109
110
		# remove comment and strip
111
		line = re.sub('//[\S \t]*', '', line)
112
		line = line.strip(' \t')
113
		# remvoe \r 
114
		line = line.rstrip()
115
		line += '\n'
116
		# remove preprocessor directive unnecessary whitespace
117
		line = re.sub('^\s*#\s*', '#', line)
118
		line = re.sub('^\s*#(.+?)[ \t]+', r'#\1 ', line)
119
120
		# remove unnecessary whitespace
121
		line = re.sub('\s+(".*?")', r' \1', line)
122
		line = re.sub(r'\)\s+>', r')>', line)
123
		line = re.sub(';[ \t]+', ';', line)
124
		line = re.sub('[ \t]+', ' ', line)
125
		line = re.sub('([\w)\]]+)\s+([&|\+\-<>=\?]+)[ \t]+([^>])', r'\1\2\3', line)
126
		line = re.sub('\s*([{\+\-\*/%=<>&|!]+=)[ \t]*', r'\1', line)
127
		line = re.sub('<\s+(\w)', r'<\1', line)
128
		line = re.sub('\s+:[ \t]+(\w)', r':\1', line)
129
		line = re.sub('\s*,[ \t]*', ',', line)
130
		line = re.sub('\s*\)', ')', line)
131
		line = re.sub('\)\s+{', '){', line)
132
		line = re.sub('\)\s+const', ')const', line)
133
		if not re.match('#define\s+.*\s+{.*', line):
134
			line = re.sub('\s*{\s*', '{', line)
135
			line = re.sub('\s*}\s*', '}', line)
136
137
		# define HOGE(x) vs define HOGE (x)
138
		m = re.match('^#define\s+(\w+)\s+(\([^)]*?\))$', line)
139
		line = re.sub('\s*\([ \t]*', '(', line)
140
		if m:
141
			line = re.sub('^#define\s+(\w+)(\([^)]*?\))$', r'#define \1 \2', line)
142
		else:
143
			line = re.sub('^#define\s+(\w+\([^)]*?\))(\S+)', r'#define \1 \2', line)
144
145
		# 0x00000X => 0xX
146
		line = re.sub('0x0+([0-9A-Fa-f])', r'0x\1', line)
147
		# 0x0 => 0
148
		line = re.sub('0x([0-9])([^0-9A-Fa-f])', r'\1\2', line)
149
150
		# string restore
151
		line = self.RestoreStrings(line, str_l)
152
153
		line = re.sub('^#define\s+(\w+)=', r'#define \1 =', line)
154
		return line
155
	
156
	def Flush(self, output_file):
157
		if len(self.store_line) > 0:
158
			output_file.write(self.store_line.strip())
159
			output_file.write('\n')
160
			self.store_line = ""
161
162
	def Translate(self, root, filename, output, output_dir, minimum):
163
		output_file = codecs.open(os.path.join(output_dir, output), 'w', 'utf-8-sig')
164
		processed_files = set();
165
		# fused-min not support gtest switch
166
		if minimum:
167
			processed_files.add(os.path.normpath(os.path.join(root, "gtest/iutest_switch.hpp")))
168
		def ProcessFile(curr, filename, fileset, minimum):
169
			path = os.path.join(root, filename)
170
			if not os.path.exists(path):
171
				path = os.path.join(curr, filename)
172
		
173
			path = os.path.normpath(path)
174
			if path in fileset:
175
				return
176
		
177
			find_ifdef = False;
178
			c_comment = False;
179
			fileset.add(path)
180
			for line in codecs.open(path, 'r', 'utf-8-sig'):
181
				line = re.sub('/\*.*?\*/', '', line)
182
				m = self.INCLUDE_REGEX.match(line)
183
				if m:
184
					self.Flush(output_file)
185
					include_file = m.group(1)
186
					if find_ifdef:
187
						s = set(fileset)
188
						ProcessFile(os.path.dirname(path), include_file, s, minimum)
189
					else:
190
						ProcessFile(os.path.dirname(path), include_file, fileset, minimum)
191
				else:
192
					find_ifdef = bool(self.IFDEF_REGEX.match(line))
193
					if minimum:
194
						line = self.Minimze(line)
195
						if len(line):
196
							if self.PREPRO_REGEX.match(self.store_line):
197
								line = line.strip();
198
								if line.endswith('\\'):
199
									self.StoreMinimze(line.rstrip(r'\\'))
200
								else:
201
									self.StoreMinimze(line)
202
									self.Flush(output_file)
203
							elif self.PREPRO_REGEX.match(line):
204
								self.Flush(output_file)
205
								if line.strip().endswith('\\'):
206
									self.StoreMinimze(line.strip().rstrip(r'\\'))
207
								else:
208
									output_file.write(line)
209
							else:
210
								strip_line = line.strip();
211
								self.StoreMinimze(strip_line)
212
								#if not re.match('.*[{};\(\)<>]$', strip_line):
213
								#	self.Flush(output_file)
214
								if re.match('.*(:|IUTEST_CXX_DEFAULT_FUNCTION)$', strip_line):
215
									self.store_line += " "
216
					else:
217
						output_file.write(line)
218
			self.Flush(output_file)
219
220
		ProcessFile(root, filename, processed_files, minimum)
221
		output_file.close()
222
223
def FusedSrc(root, filename, output, output_dir, minimum):
224
	f = IutestFused()
225
	f.Translate(root, filename, output, output_dir, minimum)
226
227
def FusedAll(root, output_dir):
228
	FusedSrc(root, 'iutest.hpp', 'iutest.hpp', output_dir, False)
229
	FusedSrc(root, 'iutest.hpp', 'iutest.min.hpp', output_dir, True)
230
231
def main():
232
	argc = len(sys.argv)
233
	if argc == 2:
234
		FusedAll(IUTEST_INCLUDE_DIR, sys.argv[1])
235
	elif argc == 3:
236
		FusedAll(os.path.join(sys.argv[1], "include"), sys.argv[2])
237
	else:
238
		print(__doc__)
239
		sys.exit(1)
240
241
if __name__ == '__main__':
242
	main()
243
244