Subversion Repositories Kolibri OS

Rev

Rev 8846 | Rev 8856 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
8834 Boppan 1
import re
8825 Boppan 2
import os
8837 Boppan 3
import argparse
8825 Boppan 4
 
8834 Boppan 5
# Parameters
8837 Boppan 6
# Path to doxygen folder to make doxygen files in: -o 
8834 Boppan 7
doxygen_src_path = 'docs/doxygen'
8837 Boppan 8
# Remove generated doxygen files: --clean
9
clean_generated_stuff = False
10
# Dump all defined symbols: --dump
8834 Boppan 11
dump_symbols = False
8837 Boppan 12
# Print symbol stats: --stats
8834 Boppan 13
print_stats = False
8855 Boppan 14
# Do not write warnings file: --nowarn
15
enable_warnings = True
8825 Boppan 16
 
8837 Boppan 17
# Constants
18
link_root = "http://websvn.kolibrios.org/filedetails.php?repname=Kolibri+OS&path=/kernel/trunk"
19
 
8855 Boppan 20
# Warning list
21
warnings = ""
22
 
8837 Boppan 23
# Parse arguments
24
parser = argparse.ArgumentParser()
25
parser.add_argument("-o", help="Doxygen output folder")
26
parser.add_argument("--clean", help="Remove generated files", action="store_true")
27
parser.add_argument("--dump", help="Dump all defined symbols", action="store_true")
28
parser.add_argument("--stats", help="Print symbol stats", action="store_true")
8855 Boppan 29
parser.add_argument("--nowarn", help="Do not write warnings file", action="store_true")
8837 Boppan 30
args = parser.parse_args()
31
doxygen_src_path = args.o if args.o else 'docs/doxygen'
32
clean_generated_stuff = args.clean
33
dump_symbols = args.dump
34
print_stats = args.stats
8855 Boppan 35
enable_warnings = not args.nowarn
8837 Boppan 36
 
8825 Boppan 37
# kernel_structure["filename"] = {
38
#   [ [],    # [0] Variables - [ line, name ]
39
#     [],    # [1] Macros - [ line, name ]
40
#     [],    # [2] Procedures - [ line, name ]
41
#     [],    # [3] Labels - [ line, name ]
42
#     [] ] } # [4] Structures - [ line, name ]
43
VARIABLES = 0
44
MACROS = 1
45
PROCEDURES = 2
46
LABELS = 3
47
STRUCTURES = 4
48
kernel_structure = {}
49
 
8855 Boppan 50
class AsmVariable:
51
	def __init__(self, line, name, type, init, comment, line_span):
52
		self.line = line
53
		self.name = name
54
		self.type = type
55
		self.init = init
56
		self.comment = comment # Comment after the definition (a dd 0 ; Comment)
57
		self.line_span = line_span # How much .asm lines its definition takes
58
 
59
class AsmFunction:
60
	def __init__(self, line, name):
61
		self.line = line
62
		self.name = name
63
 
64
class AsmLabel:
65
	def __init__(self, line, name):
66
		self.line = line
67
		self.name = name
68
 
69
class AsmMacro:
70
	def __init__(self, line, name, comment, args):
71
		self.line = line
72
		self.name = name
73
		self.comment = comment
74
		self.args = args
75
 
76
class AsmStruct:
77
	def __init__(self, line, name):
78
		self.line = line
79
		self.name = name
80
 
81
def parse_variable(asm_file_name, lines, line_idx):
82
	global warnings
83
 
84
	def curr():
85
		try: return line[i]
86
		except: return ''
87
 
88
	# Returns current and then increments current index
89
	def step():
90
		nonlocal i
91
		c = curr()
92
		i += 1
93
		return c
94
 
95
	line = lines[line_idx]
96
	i = 0
97
	# Skip first spaces
98
	while curr().isspace(): step()
99
	# Get name
100
	name = ""
101
	while curr().isalnum() or curr() == '_' or curr() == '.': name += step()
102
	# Skip spaces after variable name
103
	while curr().isspace(): step()
104
	# Get type specifier (db, dd, etc.)
105
	type = ""
106
	while curr().isalnum() or curr() == '_': type += step()
107
	# Skip spaces after type specifier
108
	while curr().isspace(): step()
109
	# Get initial value (everything up to end of the line or comment)
110
	init = ""
111
	while curr() and curr() != ';': init += step()
112
	# Get comment
113
	comment = ""
114
	if curr() == ';':
115
		step() # Skip ';'
116
		while curr(): comment += step()
117
	# Process type
118
	if type == "db": type = "byte"
119
	elif type == "dw": type = "word"
120
	elif type == "dd": type = "dword"
121
	elif type == "dq": type = "qword"
122
	else: raise Exception(f"Unexpected type: '{type}' (i = {i})")
123
	# Process comment
124
	if comment == "": comment = "Undocumented"
125
	else:
126
		comment = comment.lstrip()
127
		if (len(comment) == 0):
128
			comment = "!!! EMPTY_COMMENT"
129
			warnings += f"{asm_file_name}:{line_idx + 1}: Empty comment in\n"
130
		if comment[0].islower():
131
			warnings += f"{asm_file_name}:{line_idx + 1}: Сomment sarting with lowercase\n"
132
	# Build the result
133
	result = AsmVariable(line_idx + 1, name, type, init, comment, 1)
134
	return (1, result)
135
 
136
def is_id(c):
137
	return c.isprintable() and c not in "+-/*=<>()[]{}:,|&~#`'\" \n\r\t\v"
138
 
139
def get_comment_begin(line):
140
	result = len(line)
141
	in_str = False
142
	for i in range(len(line)):
143
		if in_str:
144
			if line[i] == in_str: in_str = False
145
			i += 1
146
		elif line[i] == '\'' or line[i] == '\"':
147
			in_str = line[i]
148
			i += 1
149
		elif line[i] == ';':
150
			result = i
151
			break
152
		else:
153
			i += 1
154
	return result
155
 
156
def get_comment(line):
157
	return line[get_comment_begin(line):]
158
 
159
def remove_comment(line):
160
	return line[0:get_comment_begin(line)]
161
 
162
def insert_comment(line, comment):
163
	comment_begin = get_comment_begin(line)
164
	line_left = line[:get_comment_begin(line)]
165
	line_right = line[get_comment_begin(line):]
166
	return line_left + comment + line_right
167
 
168
def has_line_wrap(line):
169
	if remove_comment(line).rstrip()[-1] == '\\':
170
		return True
171
	return False
172
 
173
def remove_line_wrap(line):
174
	if remove_comment(line).rstrip()[-1] == '\\':
175
		return remove_comment(line).rstrip()[:-1]
176
	return line
177
 
178
def parse_macro(asm_file_name, lines, line_idx):
179
	line_idx_orig = line_idx
180
	global warnings
181
 
182
	def curr():
183
		try: return line[i]
184
		except: return ''
185
 
186
	# Returns current and then increments current index
187
	def step():
188
		nonlocal i
189
		c = curr()
190
		i += 1
191
		return c
192
 
193
	line = lines[line_idx]
194
	# Handle line wraps ('\' at the end)
195
	while has_line_wrap(line):
196
		next_line = lines[line_idx + 1]
197
		prev_line_comment = get_comment(line)
198
		line = remove_line_wrap(line) + insert_comment(next_line, prev_line_comment)
199
		line_idx += 1
200
 
201
	i = 0
202
	# Skip first spaces
203
	while curr().isspace(): step()
204
	# Read "macro" keyword
205
	keyword = ""
206
	while is_id(curr()): keyword += step()
207
	if keyword != "macro": raise Exception(f"Not a macro: {line}")
208
	# Skip spaces after "macro"
209
	while curr().isspace(): step()
210
	# Read macro name
211
	name = ""
212
	while curr() and not curr().isspace(): name += step()
213
	# Skip spaces after macro name
214
	while curr().isspace(): step()
215
	# Find all arguments
216
	args = []
217
	arg = ''
218
	while curr() and curr() != ';' and curr() != '{':
219
		# Collect identifier
220
		if is_id(curr()):
221
			arg += step()
222
		# Save the collected identifier
223
		elif curr() == ',':
224
			args.append(arg)
225
			arg = ''
226
			step()
227
		# Just push the '['
228
		elif curr() == '[':
229
			args.append(step())
230
		# Just push the identifier and get ']' ready to be pushed on next comma
231
		elif curr() == ']':
232
			args.append(arg)
233
			arg = step()
234
		# Just push the identifier and get '*' ready to be pushed on next comma
235
		elif curr() == '*':
236
			args.append(arg)
237
			arg = step()
238
		# Just skip whitespaces
239
		elif curr().isspace():
240
			step()
241
		# Something unexpected
242
		else:
243
			raise Exception(f"Unexpected symbol '{curr()}' at index #{i} " +
244
			                f"in the macro declaration:\n'{line}'")
245
	if arg != '':
246
		args.append(arg)
247
	if len(args) > 0:
248
		print(line, args)
249
	# Find a comment if any
250
	comment = ""
251
	while curr() and curr() != ';': step()
252
	if curr() == ';':
253
		step()
254
		while curr(): comment += step()
255
	# Find end of the macro
256
	end_of_macro = False
257
	while not end_of_macro:
258
		line = lines[line_idx]
259
		rbraces = re.finditer('}', line)
260
		for rbrace_match in rbraces:
261
			rbrace_idx = rbrace_match.start()
262
			if line[rbrace_idx - 1] != '\\':
263
				end_of_macro = True
264
		line_idx += 1
265
	# Process comment
266
	if comment != "":
267
		comment = comment.lstrip()
268
		if (len(comment) == 0):
269
			comment = "!!! EMPTY_COMMENT"
270
			warnings += f"{asm_file_name}:{line_idx + 1}: Empty comment in\n"
271
		if comment[0].islower():
272
			warnings += f"{asm_file_name}:{line_idx + 1}: Сomment sarting with lowercase\n"
273
	# Build the output
274
	line_span = line_idx - line_idx_orig + 1
275
	result = AsmMacro(line_idx_orig, name, comment, args)
276
	return (line_span, result)
277
 
8825 Boppan 278
def get_declarations(asm_file_contents, asm_file_name):
8842 Boppan 279
	asm_file_name = asm_file_name.replace("./", "")
8825 Boppan 280
	kernel_structure[asm_file_name] = [ [], [], [], [], [] ]
281
 
8855 Boppan 282
	variable_pattern = re.compile(r'^\s*[\w\.]+\s+d[bwdq]\s+.*')
8825 Boppan 283
	macro_pattern = re.compile(r'^\s*macro\s+([\w]+).*')
284
	proc_pattern = re.compile(r'^\s*proc\s+([\w\.]+).*')
285
	label_pattern = re.compile(r'^(?!;)\s*([\w\.]+):.*')
286
	struct_pattern = re.compile(r'^\s*struct\s+([\w]+).*')
287
 
288
	line_idx = 0
289
	lines = asm_file_contents.splitlines()
290
	while line_idx < len(lines):
291
		line = lines[line_idx]
292
 
8855 Boppan 293
		if variable_pattern.match(line):
294
			(skip_lines, var) = parse_variable(asm_file_name, lines, line_idx)
295
			kernel_structure[asm_file_name][VARIABLES].append(var)
296
			line_idx += skip_lines
8825 Boppan 297
			continue
298
 
299
		match = macro_pattern.findall(line)
300
		if len(match) > 0:
8855 Boppan 301
			(skip_lines, macro) = parse_macro(asm_file_name, lines, line_idx)
302
			kernel_structure[asm_file_name][MACROS].append(macro)
303
			line_idx += skip_lines
8825 Boppan 304
			continue
305
 
306
		match = proc_pattern.findall(line)
307
		if len(match) > 0:
308
			proc_name = match[0]
8855 Boppan 309
			kernel_structure[asm_file_name][PROCEDURES].append(AsmFunction(line_idx + 1, proc_name))
8825 Boppan 310
			line_idx += 1
311
			continue
312
 
313
		match = label_pattern.findall(line)
314
		if len(match) > 0:
315
			label_name = match[0]
316
			# Don't count local labels
317
			if label_name[0] != '.':
8855 Boppan 318
				kernel_structure[asm_file_name][LABELS].append(AsmLabel(line_idx + 1, label_name))
8825 Boppan 319
				line_idx += 1
320
				continue
321
 
322
		match = struct_pattern.findall(line)
323
		if len(match) > 0:
324
			struct_name = match[0]
8855 Boppan 325
			kernel_structure[asm_file_name][STRUCTURES].append(AsmStruct(line_idx + 1, struct_name))
8825 Boppan 326
			end_of_struct = False
327
			while not end_of_struct:
328
				line = lines[line_idx]
329
				if re.match(r"^ends$", line) != None:
330
					end_of_struct = True
331
				line_idx += 1
332
			continue
333
 
334
		line_idx += 1
335
 
8834 Boppan 336
def handle_file(handled_files, asm_file_name, subdir = "."):
8841 Boppan 337
	if dump_symbols:
338
		print(f"Handling {asm_file_name}")
8825 Boppan 339
	handled_files.append(asm_file_name)
340
	try:
341
		asm_file_contents = open(asm_file_name, "r", encoding="utf-8").read()
342
	except:
343
		return
344
	get_declarations(asm_file_contents, asm_file_name)
345
	include_directive_pattern_1 = re.compile(r'include "(.*)"')
346
	include_directive_pattern_2 = re.compile(r'include \'(.*)\'')
347
	includes = include_directive_pattern_1.findall(asm_file_contents)
348
	includes += include_directive_pattern_2.findall(asm_file_contents)
349
	for include in includes:
350
		include = include.replace('\\', '/');
351
		full_path = subdir + '/' + include;
352
		if full_path not in handled_files:
353
			new_subdir = full_path.rsplit('/', 1)[0]
8834 Boppan 354
			handle_file(handled_files, full_path, new_subdir)
8825 Boppan 355
	return handled_files
356
 
357
kernel_files = []
358
 
8834 Boppan 359
handle_file(kernel_files, "./kernel.asm");
8825 Boppan 360
 
8834 Boppan 361
if dump_symbols:
362
	for source in kernel_structure:
363
		print(f"File: {source}")
364
		if len(kernel_structure[source][VARIABLES]) > 0:
365
			print(" Variables:")
366
			for variable in kernel_structure[source][VARIABLES]:
8855 Boppan 367
				print(f"  {variable.line}: {variable.name}")
8834 Boppan 368
		if len(kernel_structure[source][PROCEDURES]) > 0:
369
			print(" Procedures:")
370
			for procedure in kernel_structure[source][PROCEDURES]:
8855 Boppan 371
				print(f"  {procedure.line}: {procedure.name}")
8834 Boppan 372
		if len(kernel_structure[source][LABELS]) > 0:
373
			print(" Global labels:")
374
			for label in kernel_structure[source][LABELS]:
8855 Boppan 375
				print(f"  {label.line}: {label.name}")
8834 Boppan 376
		if len(kernel_structure[source][MACROS]) > 0:
377
			print(" Macroses:")
378
			for macro in kernel_structure[source][MACROS]:
8855 Boppan 379
				print(f"  {macro.line}: {macro.name}")
8834 Boppan 380
		if len(kernel_structure[source][STRUCTURES]) > 0:
381
			print(" Structures:")
382
			for struct in kernel_structure[source][STRUCTURES]:
8855 Boppan 383
				print(f"  {struct.line}: {struct.name}")
8825 Boppan 384
 
8834 Boppan 385
if print_stats:
386
	# Collect stats
387
	var_count = 0
388
	proc_count = 0
389
	label_count = 0
390
	macro_count = 0
391
	struct_count = 0
8825 Boppan 392
 
8834 Boppan 393
	for source in kernel_structure:
394
		var_count += len(kernel_structure[source][VARIABLES])
395
		proc_count += len(kernel_structure[source][PROCEDURES])
396
		label_count += len(kernel_structure[source][LABELS])
397
		macro_count += len(kernel_structure[source][MACROS])
398
		struct_count += len(kernel_structure[source][STRUCTURES])
8825 Boppan 399
 
8841 Boppan 400
	print(f"File count: {len(kernel_structure)}")
8834 Boppan 401
	print(f"Variable count: {var_count}")
402
	print(f"Procedures count: {proc_count}")
403
	print(f"Global labels count: {label_count}")
404
	print(f"Macroses count: {macro_count}")
405
	print(f"Structures count: {struct_count}")
8825 Boppan 406
 
8834 Boppan 407
print(f"Writing doumented sources to {doxygen_src_path}")
408
 
409
created_files = []
410
 
8842 Boppan 411
def write_something(source, somehing):
8834 Boppan 412
	full_path = doxygen_src_path + '/' + source
413
	# Remove the file on first access if it was created by previous generation
414
	if full_path not in created_files:
415
		if os.path.isfile(full_path):
416
			os.remove(full_path)
417
		created_files.append(full_path)
418
	# Only remove the file on 'clean_generated_stuff' flag (removed above, just return)
419
	if clean_generated_stuff: return
420
	# Create directories need for the file
421
	os.makedirs(os.path.dirname(full_path), exist_ok=True)
422
	f = open(full_path, "a")
8842 Boppan 423
	f.write(somehing)
8834 Boppan 424
	f.close()
425
 
8855 Boppan 426
def write_variable(source, variable):
427
	line = variable.line
428
	type = variable.type
429
	init = variable.init
430
	brief = variable.comment
431
	name = variable.name.replace(".", "_")
8842 Boppan 432
	something = (f"/**\n" +
433
	             f" * @brief {brief}\n" +
434
	             f" * @par Initial value\n" +
435
	             f" * {init}\n" +
436
	             f" * @par Source\n" +
437
	             f" * {source}:{line}\n" +
438
	             f" */\n" +
439
	             f"{type} {name};\n\n")
440
	write_something(source, something)
441
 
442
def write_procedure(source, line, name, brief = "Undocumented"):
443
	name = name.replace(".", "_")
444
	something = (f"/**\n" +
445
	             f" * @brief {brief}\n" +
446
	             f" * @par Source\n" +
447
	             f" * {source}:{line}\n" +
448
	             f" */\n" +
449
	             f"void {name}();\n\n")
450
	write_something(source, something)
451
 
8844 Boppan 452
def write_label(source, line, name, brief = "Undocumented"):
453
	name = name.replace(".", "_")
454
	something = (f"/**\n" +
455
	             f" * @brief {brief}\n" +
456
	             f" * @par Source\n" +
457
	             f" * {source}:{line}\n" +
458
	             f" */\n" +
459
	             f"void {name}();\n\n")
460
	write_something(source, something)
461
 
8855 Boppan 462
def write_macro(source, macro):
463
	if macro.comment == "": brief = "Undocumented"
464
	else: brief = macro.comment
465
	line = macro.line
466
	name = macro.name.replace(".", "_").replace("@", "_")
467
	# Construct arg list without '['s, ']'s and '*'s
468
	args = [arg for arg in macro.args if arg not in "[]*"]
469
	# Construct C-like arg list
470
	arg_list = ""
471
	if len(args) > 0:
472
		arg_list += '('
473
		argc = 0
474
		for arg in args:
475
			if argc != 0:
476
				arg_list += ", "
477
			arg_list += arg
478
			argc += 1
479
		arg_list += ')'
480
 
8846 Boppan 481
	something = (f"/**\n" +
482
	             f" * @def {name}\n" +
483
	             f" * @brief {brief}\n" +
484
	             f" * @par Source\n" +
485
	             f" * {source}:{line}\n" +
8855 Boppan 486
	             f" */\n#define {name}{arg_list}\n\n")
8846 Boppan 487
	write_something(source, something)
488
 
489
def write_structure(source, line, name, brief = "Undocumented"):
490
	name = name.replace(".", "_")
491
	something = (f"/**\n" +
492
	             f" * @struct {name}\n" +
493
	             f" * @brief {brief}\n" +
494
	             f" * @par Source\n" +
495
	             f" * {source}:{line}\n" +
496
	             f" */\nstruct {name}" + " {};\n\n")
497
	write_something(source, something)
498
 
8834 Boppan 499
i = 1
500
for source in kernel_structure:
501
	# Print progress: current/total
502
	print(f"{i}/{len(kernel_structure)} Writing {source}")
503
	# Write variables doxygen of the source file
504
	if len(kernel_structure[source][VARIABLES]) > 0:
505
		for variable in kernel_structure[source][VARIABLES]:
8855 Boppan 506
			write_variable(source, variable)
8842 Boppan 507
	if len(kernel_structure[source][PROCEDURES]) > 0:
508
		for procedure in kernel_structure[source][PROCEDURES]:
8855 Boppan 509
			write_procedure(source, procedure.line, procedure.name)
8844 Boppan 510
	if len(kernel_structure[source][LABELS]) > 0:
511
		for label in kernel_structure[source][LABELS]:
8855 Boppan 512
			write_label(source, label.line, label.name)
8846 Boppan 513
	if len(kernel_structure[source][MACROS]) > 0:
514
		for macro in kernel_structure[source][MACROS]:
8855 Boppan 515
			write_macro(source, macro)
8846 Boppan 516
	if len(kernel_structure[source][STRUCTURES]) > 0:
517
		for structure in kernel_structure[source][STRUCTURES]:
8855 Boppan 518
			write_structure(source, structure.line, structure.name)
8834 Boppan 519
	i += 1
8855 Boppan 520
 
521
if enable_warnings:
522
	open('asmxygen.txt', "w", encoding = "utf-8").write(warnings)