Subversion Repositories Kolibri OS

Rev

Rev 9398 | Rev 9400 | 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
8957 Boppan 4
import sys
8990 Boppan 5
import pickle
8825 Boppan 6
 
8834 Boppan 7
# Parameters
8837 Boppan 8
# Path to doxygen folder to make doxygen files in: -o 
8834 Boppan 9
doxygen_src_path = 'docs/doxygen'
8837 Boppan 10
# Remove generated doxygen files: --clean
11
clean_generated_stuff = False
12
# Dump all defined symbols: --dump
8834 Boppan 13
dump_symbols = False
8837 Boppan 14
# Print symbol stats: --stats
8834 Boppan 15
print_stats = False
8855 Boppan 16
# Do not write warnings file: --nowarn
17
enable_warnings = True
8825 Boppan 18
 
8837 Boppan 19
# Constants
20
link_root = "http://websvn.kolibrios.org/filedetails.php?repname=Kolibri+OS&path=/kernel/trunk"
21
 
8957 Boppan 22
# fasm keywords
23
keywords = [
9398 Boppan 24
    "align", "equ", "org", "while", "load", "store", "times", "repeat",
25
    "display", "err", "assert", "if", "aaa", "aad", "aam", "aas", "adc",
26
    "add", "addpd", "addps", "addsd", "addss", "addsubpd", "addsubps", "adox",
27
    "aesdeclast", "aesenc", "aesenclast", "aesimc", "aeskeygenassist", "and",
28
    "andnpd", "andnps", "andpd", "andps", "arpl", "bextr", "blendpd",
29
    "blendvpd", "blendvps", "blsi", "blsmsk", "blsr", "bndcl", "bndcn",
30
    "bndldx", "bndmk", "bndmov", "bndstx", "bound", "bsf", "bsr", "bswap",
31
    "btc", "btr", "bts", "bzhi", "call", "cbw", "cdq", "cdqe", "clac", "clc",
32
    "cldemote", "clflush", "clflushopt", "cli", "clts", "clwb", "cmc", "cmova",
33
    "cmovb", "cmovbe", "cmovc", "cmove", "cmovg", "cmovge", "cmovl", "cmovle",
34
    "cmovnae", "cmovnb", "cmovnbe", "cmovnc", "cmovne", "cmovng", "cmovnge",
35
    "cmovnle", "cmovno", "cmovnp", "cmovns", "cmovnz", "cmovo", "cmovp",
36
    "cmovpo", "cmovs", "cmovz", "cmp", "cmppd", "cmpps", "cmps", "cmpsb",
37
    "cmpsd", "cmpsq", "cmpss", "cmpsw", "cmpxchg", "cmpxchg16b", "cmpxchg8b",
38
    "comiss", "cpuid", "cqo", "crc32", "cvtdq2pd", "cvtdq2ps", "cvtpd2dq",
39
    "cvtpd2ps", "cvtpi2pd", "cvtpi2ps", "cvtps2dq", "cvtps2pd", "cvtps2pi",
40
    "cvtsd2ss", "cvtsi2sd", "cvtsi2ss", "cvtss2sd", "cvtss2si", "cvttpd2dq",
41
    "cvttps2dq", "cvttps2pi", "cvttsd2si", "cvttss2si", "cwd", "cwde", "daa",
42
    "dec", "div", "divpd", "divps", "divsd", "divss", "dppd", "dpps", "emms",
43
    "extractps", "f2xm1", "fabs", "fadd", "faddp", "fbld", "fbstp", "fchs",
44
    "fcmova", "fcmovae", "fcmovb", "fcmovbe", "fcmovc", "fcmove", "fcmovg",
45
    "fcmovl", "fcmovle", "fcmovna", "fcmovnae", "fcmovnb", "fcmovnbe",
46
    "fcmovne", "fcmovng", "fcmovnge", "fcmovnl", "fcmovnle", "fcmovno",
47
    "fcmovns", "fcmovnz", "fcmovo", "fcmovp", "fcmovpe", "fcmovpo", "fcmovs",
48
    "fcom", "fcomi", "fcomip", "fcomp", "fcompp", "fcos", "fdecstp", "fdiv",
49
    "fdivr", "fdivrp", "ffree", "fiadd", "ficom", "ficomp", "fidiv", "fidivr",
50
    "fimul", "fincstp", "finit", "fist", "fistp", "fisttp", "fisub", "fisubr",
51
    "fld1", "fldcw", "fldenv", "fldl2e", "fldl2t", "fldlg2", "fldln2", "fldpi",
52
    "fmul", "fmulp", "fnclex", "fninit", "fnop", "fnsave", "fnstcw", "fnstenv",
53
    "fpatan", "fprem", "fprem1", "fptan", "frndint", "frstor", "fsave",
54
    "fsin", "fsincos", "fsqrt", "fst", "fstcw", "fstenv", "fstp", "fstsw",
55
    "fsubp", "fsubr", "fsubrp", "ftst", "fucom", "fucomi", "fucomip", "fucomp",
56
    "fwait", "fxam", "fxch", "fxrstor", "fxsave", "fxtract", "fyl2x",
57
    "gf2p8affineinvqb", "gf2p8affineqb", "gf2p8mulb", "haddpd", "haddps",
58
    "hsubpd", "hsubps", "idiv", "imul", "in", "inc", "ins", "insb", "insd",
59
    "insw", "int", "int1", "int3", "into", "invd", "invlpg", "invpcid", "iret",
60
    "jmp", "ja", "jae", "jb", "jbe", "jc", "jcxz", "jecxz", "je", "jg", "jge",
61
    "jle", "jna", "jnae", "jnb", "jnbe", "jnc", "jne", "jng", "jnge", "jnl",
62
    "jno", "jnp", "jns", "jnz", "jo", "jp", "jpe", "jpo", "js", "jz", "kaddb",
63
    "kaddq", "kaddw", "kandb", "kandd", "kandnb", "kandnd", "kandnq", "kandnw",
64
    "kandw", "kmovb", "kmovd", "kmovq", "kmovw", "knotb", "knotd", "knotq",
65
    "korb", "kord", "korq", "kortestb", "kortestd", "kortestq", "kortestw",
66
    "kshiftlb", "kshiftld", "kshiftlq", "kshiftlw", "kshiftrb", "kshiftrd",
67
    "kshiftrw", "ktestb", "ktestd", "ktestq", "ktestw", "kunpckbw", "kunpckdq",
68
    "kxnorb", "kxnord", "kxnorq", "kxnorw", "kxorb", "kxord", "kxorq", "kxorw",
69
    "lar", "lddqu", "ldmxcsr", "lds", "lea", "leave", "les", "lfence", "lfs",
70
    "lgs", "lidt", "lldt", "lmsw", "lock", "lods", "lodsb", "lodsd", "lodsq",
71
    "loop", "loopa", "loopae", "loopb", "loopbe", "loopc", "loope", "loopg",
72
    "loopl", "loople", "loopna", "loopnae", "loopnb", "loopnbe", "loopnc",
73
    "loopng", "loopnge", "loopnl", "loopnle", "loopno", "loopnp", "loopns",
74
    "loopo", "loopp", "looppe", "looppo", "loops", "loopz", "lsl", "lss",
75
    "lzcnt", "maskmovdqu", "maskmovq", "maxpd", "maxps", "maxsd", "maxss",
76
    "minpd", "minps", "minsd", "minss", "monitor", "mov", "movapd", "movaps",
77
    "movd", "movddup", "movdir64b", "movdiri", "movdq2q", "movdqa", "movdqu",
78
    "movhpd", "movhps", "movlhps", "movlpd", "movlps", "movmskpd", "movmskps",
79
    "movntdqa", "movnti", "movntpd", "movntps", "movntq", "movq", "movq",
80
    "movs", "movsb", "movsd", "movsd", "movshdup", "movsldup", "movsq",
81
    "movsw", "movsx", "movsxd", "movupd", "movups", "movzx", "mpsadbw", "mul",
82
    "mulps", "mulsd", "mulss", "mulx", "mwait", "neg", "nop", "not", "or",
83
    "orps", "out", "outs", "outsb", "outsd", "outsw", "pabsb", "pabsd",
84
    "pabsw", "packssdw", "packsswb", "packusdw", "packuswb", "paddb", "paddd",
85
    "paddsb", "paddsw", "paddusb", "paddusw", "paddw", "palignr", "pand",
86
    "pause", "pavgb", "pavgw", "pblendvb", "pblendw", "pclmulqdq", "pcmpeqb",
87
    "pcmpeqq", "pcmpeqw", "pcmpestri", "pcmpestrm", "pcmpgtb", "pcmpgtd",
88
    "pcmpgtw", "pcmpistri", "pcmpistrm", "pdep", "pext", "pextrb", "pextrd",
89
    "pextrw", "phaddd", "phaddsw", "phaddw", "phminposuw", "phsubd", "phsubsw",
90
    "pinsrb", "pinsrd", "pinsrq", "pinsrw", "pmaddubsw", "pmaddwd", "pmaxsb",
91
    "pmaxsq", "pmaxsw", "pmaxub", "pmaxud", "pmaxuq", "pmaxuw", "pminsb",
92
    "pminsq", "pminsw", "pminub", "pminud", "pminuq", "pminuw", "pmovmskb",
93
    "pmovzx", "pmuldq", "pmulhrsw", "pmulhuw", "pmulhw", "pmulld", "pmullq",
94
    "pmuludq", "pop", "popa", "popad", "popcnt", "popf", "popfd", "popfq",
95
    "prefetchw", "prefetchh", "psadbw", "pshufb", "pshufd", "pshufhw",
96
    "pshufw", "psignb", "psignd", "psignw", "pslld", "pslldq", "psllq",
97
    "psrad", "psraq", "psraw", "psrld", "psrldq", "psrlq", "psrlw", "psubb",
98
    "psubq", "psubsb", "psubsw", "psubusb", "psubusw", "psubw", "ptest",
99
    "punpckhbw", "punpckhdq", "punpckhqdq", "punpckhwd", "punpcklbw",
100
    "punpcklqdq", "punpcklwd", "push", "pushw", "pushd", "pusha", "pushad",
101
    "pushfd", "pushfq", "pxor", "rcl", "rcpps", "rcpss", "rcr", "rdfsbase",
102
    "rdmsr", "rdpid", "rdpkru", "rdpmc", "rdrand", "rdseed", "rdtsc", "rdtscp",
103
    "repe", "repne", "repnz", "repz", "ret", "rol", "ror", "rorx", "roundpd",
104
    "roundsd", "roundss", "rsm", "rsqrtps", "rsqrtss", "sahf", "sal", "sar",
105
    "sbb", "scas", "scasb", "scasd", "scasw", "seta", "setae", "setb", "setbe",
106
    "sete", "setg", "setge", "setl", "setle", "setna", "setnae", "setnb",
107
    "setnc", "setne", "setng", "setnge", "setnl", "setnle", "setno", "setnp",
108
    "setnz", "seto", "setp", "setpe", "setpo", "sets", "setz", "sfence",
109
    "sha1msg1", "sha1msg2", "sha1nexte", "sha1rnds4", "sha256msg1",
110
    "sha256rnds2", "shl", "shld", "shlx", "shr", "shrd", "shrx", "shufpd",
111
    "sidt", "sldt", "smsw", "sqrtpd", "sqrtps", "sqrtsd", "sqrtss", "stac",
112
    "std", "sti", "stmxcsr", "stos", "stosb", "stosd", "stosq", "stosw", "str",
113
    "subpd", "subps", "subsd", "subss", "swapgs", "syscall", "sysenter",
114
    "sysret", "test", "tpause", "tzcnt", "ucomisd", "ucomiss", "ud",
115
    "umwait", "unpckhpd", "unpckhps", "unpcklpd", "unpcklps", "valignd",
116
    "vblendmpd", "vblendmps", "vbroadcast", "vcompresspd", "vcompressps",
117
    "vcvtpd2udq", "vcvtpd2uqq", "vcvtph2ps", "vcvtps2ph", "vcvtps2qq",
118
    "vcvtps2uqq", "vcvtqq2pd", "vcvtqq2ps", "vcvtsd2usi", "vcvtss2usi",
119
    "vcvttpd2udq", "vcvttpd2uqq", "vcvttps2qq", "vcvttps2udq", "vcvttps2uqq",
120
    "vcvttss2usi", "vcvtudq2pd", "vcvtudq2ps", "vcvtuqq2pd", "vcvtuqq2ps",
121
    "vcvtusi2ss", "vdbpsadbw", "verr", "verw", "vexpandpd", "vexpandps",
122
    "vextractf32x4", "vextractf32x8", "vextractf64x2", "vextractf64x4",
123
    "vextracti32x4", "vextracti32x8", "vextracti64x2", "vextracti64x4",
124
    "vfixupimmps", "vfixupimmsd", "vfixupimmss", "vfmadd132pd", "vfmadd132ps",
125
    "vfmadd132ss", "vfmadd213pd", "vfmadd213ps", "vfmadd213sd", "vfmadd213ss",
126
    "vfmadd231ps", "vfmadd231sd", "vfmadd231ss", "vfmaddsub132pd",
127
    "vfmaddsub213pd", "vfmaddsub213ps", "vfmaddsub231pd", "vfmaddsub231ps",
128
    "vfmsub132ps", "vfmsub132sd", "vfmsub132ss", "vfmsub213pd", "vfmsub213ps",
129
    "vfmsub213ss", "vfmsub231pd", "vfmsub231ps", "vfmsub231sd", "vfmsub231ss",
130
    "vfmsubadd132ps", "vfmsubadd213pd", "vfmsubadd213ps", "vfmsubadd231pd",
131
    "vfnmadd132pd", "vfnmadd132ps", "vfnmadd132sd", "vfnmadd132ss",
132
    "vfnmadd213ps", "vfnmadd213sd", "vfnmadd213ss", "vfnmadd231pd",
133
    "vfnmadd231sd", "vfnmadd231ss", "vfnmsub132pd", "vfnmsub132ps",
134
    "vfnmsub132ss", "vfnmsub213pd", "vfnmsub213ps", "vfnmsub213sd",
135
    "vfnmsub231pd", "vfnmsub231ps", "vfnmsub231sd", "vfnmsub231ss",
136
    "vfpclassps", "vfpclasssd", "vfpclassss", "vgatherdpd", "vgatherdpd",
137
    "vgatherdps", "vgatherqpd", "vgatherqpd", "vgatherqps", "vgatherqps",
138
    "vgetexpps", "vgetexpsd", "vgetexpss", "vgetmantpd", "vgetmantps",
139
    "vgetmantss", "vinsertf128", "vinsertf32x4", "vinsertf32x8",
140
    "vinsertf64x4", "vinserti128", "vinserti32x4", "vinserti32x8",
141
    "vinserti64x4", "vmaskmov", "vmovdqa32", "vmovdqa64", "vmovdqu16",
142
    "vmovdqu64", "vmovdqu8", "vpblendd", "vpblendmb", "vpblendmd", "vpblendmq",
143
    "vpbroadcast", "vpbroadcastb", "vpbroadcastd", "vpbroadcastm",
144
    "vpbroadcastw", "vpcmpb", "vpcmpd", "vpcmpq", "vpcmpub", "vpcmpud",
145
    "vpcmpuw", "vpcmpw", "vpcompressd", "vpcompressq", "vpconflictd",
146
    "vperm2f128", "vperm2i128", "vpermb", "vpermd", "vpermi2b", "vpermi2d",
147
    "vpermi2ps", "vpermi2q", "vpermi2w", "vpermilpd", "vpermilps", "vpermpd",
148
    "vpermq", "vpermt2b", "vpermt2d", "vpermt2pd", "vpermt2ps", "vpermt2q",
149
    "vpermw", "vpexpandd", "vpexpandq", "vpgatherdd", "vpgatherdd",
150
    "vpgatherdq", "vpgatherqd", "vpgatherqd", "vpgatherqq", "vpgatherqq",
151
    "vplzcntq", "vpmadd52huq", "vpmadd52luq", "vpmaskmov", "vpmovb2m",
152
    "vpmovdb", "vpmovdw", "vpmovm2b", "vpmovm2d", "vpmovm2q", "vpmovm2w",
153
    "vpmovqb", "vpmovqd", "vpmovqw", "vpmovsdb", "vpmovsdw", "vpmovsqb",
154
    "vpmovsqw", "vpmovswb", "vpmovusdb", "vpmovusdw", "vpmovusqb", "vpmovusqd",
155
    "vpmovuswb", "vpmovw2m", "vpmovwb", "vpmultishiftqb", "vprold", "vprolq",
156
    "vprolvq", "vprord", "vprorq", "vprorvd", "vprorvq", "vpscatterdd",
157
    "vpscatterqd", "vpscatterqq", "vpsllvd", "vpsllvq", "vpsllvw", "vpsravd",
158
    "vpsravw", "vpsrlvd", "vpsrlvq", "vpsrlvw", "vpternlogd", "vpternlogq",
159
    "vptestmd", "vptestmq", "vptestmw", "vptestnmb", "vptestnmd", "vptestnmq",
160
    "vrangepd", "vrangeps", "vrangesd", "vrangess", "vrcp14pd", "vrcp14ps",
161
    "vrcp14ss", "vreducepd", "vreduceps", "vreducesd", "vreducess",
162
    "vrndscaleps", "vrndscalesd", "vrndscaless", "vrsqrt14pd", "vrsqrt14ps",
163
    "vrsqrt14ss", "vscalefpd", "vscalefps", "vscalefsd", "vscalefss",
164
    "vscatterdps", "vscatterqpd", "vscatterqps", "vshuff32x4", "vshuff64x2",
165
    "vshufi64x2", "vtestpd", "vtestps", "vzeroall", "vzeroupper", "wait",
166
    "wrfsbase", "wrgsbase", "wrmsr", "wrpkru", "xabort", "xacquire", "xadd",
167
    "xchg", "xend", "xgetbv", "xlat", "xlatb", "xor", "xorpd", "xorps",
168
    "xrstor", "xrstors", "xsave", "xsavec", "xsaveopt", "xsaves", "xsetbv",
8957 Boppan 169
]
170
 
171
fasm_types = [
172
	"db", "rb",
173
	"dw", "rw",
174
	"dd", "rd",
175
	"dp", "rp",
176
	"df", "rf",
177
	"dq", "rq",
178
	"dt", "rt",
179
	"du",
180
]
181
 
8976 Boppan 182
# Add kind flag to identifier in id2kind
183
def id_add_kind(identifier, kind):
184
	if identifier not in id2kind:
185
		id2kind[identifier] = ''
186
	id2kind[identifier] += kind
187
 
188
# Remove kind flag of identifier in id2kind
189
def id_remove_kind(identifier, kind):
190
	if identifier in id2kind:
191
		if kind in id2kind[identifier]:
192
			id2kind[identifier] = id2kind[identifier].replace(kind, '')
193
 
194
# Get kind of an identifier
195
def id_get_kind(identifier):
196
	if identifier in id2kind:
197
		return id2kind[identifier]
198
	else:
199
		return ''
200
 
8957 Boppan 201
class LegacyAsmReader:
202
	def __init__(self, file):
203
		self.file = file
204
		self.lines = open(file, "r", encoding="utf-8").readlines()
205
		self.line_idx = 0
206
		self.i = 0
207
 
208
	def curr(self):
209
		try: return self.lines[self.line_idx][self.i]
210
		except: return ''
211
 
212
	def step(self):
213
		c = self.curr()
214
		self.i += 1
215
		# Wrap the line if '\\' followed by whitespaces and/or comment
216
		while self.curr() == '\\':
217
			i_of_backslash = self.i
218
			self.i += 1
219
			while self.curr().isspace():
220
				self.i += 1
221
			if self.curr() == ';' or self.curr() == '':
222
				self.line_idx += 1
223
				self.i = 0
224
			else:
225
				# There's something other than a comment after the backslash
226
				# So don't interpret the backslash as a line wrap
227
				self.i = i_of_backslash
228
				break
229
		return c
230
 
231
	def nextline(self):
232
		c = self.curr()
233
		while c != '':
234
			c = self.step()
235
		self.line_idx += 1
236
		self.i = 0
237
 
238
	def no_lines(self):
239
		if self.line_idx >= len(self.lines):
240
			return True
241
		return False
242
 
243
	def location(self):
244
		return f"{self.file}:{self.line_idx + 1}"
245
 
246
	def skip_spaces(self):
247
		while self.curr().isspace():
248
			self.step()
249
 
250
class AsmReaderRecognizingStrings(LegacyAsmReader):
251
	def __init__(self, file):
252
		super().__init__(file)
253
		self.in_string = None
254
		self.should_recognize_strings = True
255
 
256
	def step(self):
257
		c = super().step()
258
		if self.should_recognize_strings and (c == '"' or c == "'"):
259
			# If just now we was at the double or single quotation mark
260
			# and we aren't in a string yet
261
			# then say "we are in a string openned with this quotation mark now"
262
			if self.in_string == None:
263
				self.in_string = c
264
			# If just now we was at the double or single quotation mark
265
			# and we are in the string entered with the same quotation mark
266
			# then say "we aren't in a string anymore"
267
			elif self.in_string == c:
268
				self.in_string = None
269
		return c
270
 
271
class AsmReaderReadingComments(AsmReaderRecognizingStrings):
272
	def __init__(self, file):
273
		super().__init__(file)
274
		self.status = dict()
275
		self.status_reset()
276
		self.comment = ''
277
 
278
	def status_reset(self):
279
		# If the line has non-comment code
8974 Boppan 280
		self.status_has_code = False
8957 Boppan 281
		# If the line has a comment at the end
8974 Boppan 282
		self.status_has_comment = False
8957 Boppan 283
		# Let it recognize strings further, we are definitely out of a comment
284
		self.should_recognize_strings = True
285
 
286
	def status_set_has_comment(self):
8974 Boppan 287
		self.status_has_comment = True
8957 Boppan 288
		# Don't let it recognize strings cause we are in a comment now
289
		self.should_recognize_strings = False
290
 
291
	def status_set_has_code(self):
8974 Boppan 292
		self.status_has_code = True
8957 Boppan 293
 
294
	def update_status(self):
295
		# If we aren't in a comment and we aren't in a string - say we are now in a comment if ';' met
8974 Boppan 296
		if not self.status_has_comment and not self.in_string and self.curr() == ';':
8957 Boppan 297
			self.status_set_has_comment()
298
		# Else if we are in a comment - collect the comment
8974 Boppan 299
		elif self.status_has_comment:
8957 Boppan 300
			self.comment += self.curr()
301
		# Else if there's some non-whitespace character out of a comment
302
		# then the line has code
8974 Boppan 303
		elif not self.status_has_comment and not self.curr().isspace():
8957 Boppan 304
			self.status_set_has_code()
305
 
306
	def step(self):
307
		# Get to the next character
308
		c = super().step()
309
		# Update status of the line according to the next character
310
		self.update_status()
311
		return c
312
 
313
	def nextline(self):
314
		super().nextline()
315
		# If the line we leave was not a comment-only line
316
		# then forget the collected comment
317
		# Otherwise the collected comment should be complemented by comment from next line in step()
8974 Boppan 318
		if self.status_has_code:
8957 Boppan 319
			self.comment = ''
320
		# Reset the line status (now it's the status of the new line)
321
		self.status_reset()
322
		# Set new status for this line according to the first character in the line
323
		self.update_status()
324
 
8963 Boppan 325
class AsmReaderFetchingIdentifiers(AsmReaderReadingComments):
8957 Boppan 326
	def __init__(self, file):
327
		super().__init__(file)
328
 
8963 Boppan 329
	def fetch_identifier(self):
330
		self.skip_spaces()
331
		result = ''
332
		while is_id(self.curr()):
333
			result += self.step()
334
		return result
335
 
336
class AsmReader(AsmReaderFetchingIdentifiers):
337
	def __init__(self, file):
338
		super().__init__(file)
339
 
8957 Boppan 340
class AsmElement:
341
	def __init__(self, location, name, comment):
8980 Boppan 342
		global warnings
343
 
8990 Boppan 344
		# If the element was constructed during this execution then the element is new
345
		self.new = True
8957 Boppan 346
		self.location = location
347
		self.file = self.location.split(':')[0].replace('\\', '/')
348
		self.line = self.location.split(':')[1]
8855 Boppan 349
		self.name = name
8957 Boppan 350
		self.comment = comment
351
 
8980 Boppan 352
		if self.comment == '':
353
			warnings += f'{self.location}: Undocumented element\n'
354
 
8957 Boppan 355
	def dump(self):
9030 Boppan 356
		print(f"\n{self.location}: {self.name}")
8957 Boppan 357
		print(f"{self.comment}")
358
 
359
	def emit(self, dest, doxycomment = '', declaration = ''):
8977 Boppan 360
		# Do not emit anything if the symbol is marked as hidden in its comment
361
		if '@dont_give_a_doxygen' in self.comment:
362
			return
363
 
8957 Boppan 364
		global warnings
365
		# Redefine default declaration
366
		if declaration == '':
367
			declaration = f'#define {self.name}'
368
		# Check doxycomment
369
		if not doxycomment.endswith('\n'):
370
			doxycomment += '\n'
371
		if doxycomment.split('@brief ')[1][0].islower():
372
			warnings += f"{self.location}: Brief comment starting from lowercase\n"
373
		# Build contents to emit
374
		contents = ''
375
		contents += '/**\n'
376
		contents += doxycomment
377
		contents += (f"@par Source\n" +
378
		             f"{self.file}:{self.line}\n")
379
		contents += '*/\n'
380
		contents += declaration
381
		contents += '\n\n'
382
		# Get path to file to emit this
383
		full_path = dest + '/' + self.file
384
		# Remove the file on first access if it was created by previous generation
385
		if full_path not in created_files:
386
			if os.path.isfile(full_path):
387
				os.remove(full_path)
388
			created_files.append(full_path)
389
		# Create directories need for the file
390
		os.makedirs(os.path.dirname(full_path), exist_ok=True)
391
		f = open(full_path, "a")
392
		contents = ''.join([i if ord(i) < 128 else '?' for i in contents])
393
		f.write(contents)
394
		f.close()
395
 
396
class AsmVariable(AsmElement):
397
	def __init__(self, location, name, comment, type, init):
398
		super().__init__(location, name, comment)
8855 Boppan 399
		self.type = type
400
		self.init = init
401
 
8957 Boppan 402
	def dump(self):
403
		super().dump()
9030 Boppan 404
		print(f"(Variable)\n---")
8855 Boppan 405
 
8957 Boppan 406
	def emit(self, dest):
407
		# Build doxycomment specific for the variable
408
		doxycomment = ''
409
		doxycomment += self.comment
410
		if '@brief' not in doxycomment:
411
			doxycomment = '@brief ' + doxycomment
412
		doxycomment += (f"@par Initial value\n" +
413
		                f"{self.init}\n")
414
		# Build the declaration
415
		name = self.name.replace(".", "_")
416
		var_type = self.type.replace(".", "_")
417
		declaration = f"{var_type} {name};"
418
		# Emit this
419
		super().emit(dest, doxycomment, declaration)
8855 Boppan 420
 
8957 Boppan 421
class AsmFunction(AsmElement):
8963 Boppan 422
	def __init__(self, location, name, comment, calling_convention, args, used_regs):
8957 Boppan 423
		super().__init__(location, name, comment)
8963 Boppan 424
		self.calling_convention = calling_convention
425
		self.args = args
426
		self.used_regs = used_regs
8855 Boppan 427
 
8957 Boppan 428
	def dump(self):
429
		super().dump()
9030 Boppan 430
		print(f"(Function)\n---")
8855 Boppan 431
 
8957 Boppan 432
	def emit(self, dest):
433
		# Build doxycomment specific for the variable
434
		doxycomment = ''
435
		doxycomment += self.comment
436
		if '@brief' not in doxycomment:
437
			doxycomment = '@brief ' + doxycomment
9028 Boppan 438
		# If there was no arguments, maybe that's just a label
439
		# then parse parameters from its comment
440
		if len(self.args) == 0 and '@param' in self.comment:
441
			i = 0
442
			while '@param' in self.comment[i:]:
443
				i = self.comment.index('@param', i)
444
				# Skip '@param'
445
				i += len('@param')
446
				# Skip spaces after '@param'
447
				while self.comment[i].isspace():
448
					i += 1
449
				# Get the parameter name
450
				name = ''
451
				while is_id(self.comment[i]):
452
					name += self.comment[i]
453
					i += 1
454
				# Save the parameter
455
				self.args.append((name, 'arg_t'))
8963 Boppan 456
		# Build the arg list for declaration
457
		arg_list = '('
458
		if len(self.args) > 0:
459
			argc = 0
460
			for arg in self.args:
461
				if argc != 0:
462
					arg_list += ", "
463
				arg_list += f"{arg[1]} {arg[0]}"
464
				argc += 1
465
		arg_list += ')'
8957 Boppan 466
		# Build the declaration
467
		name = self.name.replace(".", "_")
8963 Boppan 468
		declaration = f"void {name}{arg_list};"
8957 Boppan 469
		# Emit this
470
		super().emit(dest, doxycomment, declaration)
8855 Boppan 471
 
8957 Boppan 472
class AsmLabel(AsmElement):
473
	def __init__(self, location, name, comment):
474
		super().__init__(location, name, comment)
8855 Boppan 475
 
8957 Boppan 476
	def dump(self):
477
		super().dump()
9030 Boppan 478
		print(f"(Label)\n---")
8855 Boppan 479
 
8957 Boppan 480
	def emit(self, dest):
481
		# Build doxycomment specific for the variable
482
		doxycomment = ''
483
		doxycomment += self.comment
484
		if '@brief' not in doxycomment:
485
			doxycomment = '@brief ' + doxycomment
486
		# Build the declaration
487
		name = self.name.replace(".", "_")
488
		declaration = f"label {name};"
489
		# Emit this
490
		super().emit(dest, doxycomment, declaration)
8855 Boppan 491
 
8957 Boppan 492
class AsmMacro(AsmElement):
493
	def __init__(self, location, name, comment, args):
494
		super().__init__(location, name, comment)
495
		self.args = args
8855 Boppan 496
 
8957 Boppan 497
	def dump(self):
498
		super().dump()
9030 Boppan 499
		print(f"(Macro)\n---")
8855 Boppan 500
 
8957 Boppan 501
	def emit(self, dest):
502
		# Construct arg list without '['s, ']'s and '*'s
503
		args = [arg for arg in self.args if arg not in "[]*"]
504
		# Construct C-like arg list
505
		arg_list = ""
506
		if len(args) > 0:
507
			arg_list += '('
508
			argc = 0
509
			for arg in args:
510
				if argc != 0:
511
					arg_list += ", "
512
				arg_list += arg
513
				argc += 1
514
			arg_list += ')'
515
		# Build doxycomment
516
		doxycomment = ''
517
		doxycomment += self.comment
518
		if '@brief' not in doxycomment:
519
			doxycomment = '@brief ' + doxycomment
520
		# Build declaration
521
		declaration = f"#define {self.name}{arg_list}"
522
		# Emit this
523
		super().emit(dest, doxycomment, declaration)
8855 Boppan 524
 
8957 Boppan 525
class AsmStruct(AsmElement):
526
	def __init__(self, location, name, comment, members):
527
		super().__init__(location, name, comment)
528
		self.members = members
8855 Boppan 529
 
8957 Boppan 530
	def dump(self):
531
		super().dump()
9030 Boppan 532
		print(f"(Struct)\n---")
8855 Boppan 533
 
8957 Boppan 534
	def emit(self, dest):
535
		# Build doxycomment
536
		doxycomment = ''
537
		doxycomment += self.comment
538
		if '@brief' not in doxycomment:
539
			doxycomment = '@brief ' + doxycomment
8958 Boppan 540
		doxycomment += '\n'
8957 Boppan 541
		# Build declaration
8958 Boppan 542
		declaration = f"struct {self.name}" + " {\n"
543
		for member in self.members:
544
			if type(member) == AsmVariable:
545
				declaration += f'\t{member.type} {member.name}; /**< {member.comment} */\n'
546
		declaration += '};'
8957 Boppan 547
		# Emit this
548
		super().emit(dest, doxycomment, declaration)
8855 Boppan 549
 
8957 Boppan 550
class AsmUnion(AsmElement):
551
	def __init__(self, location, name, comment, members):
552
		super().__init__(location, name, comment)
553
		self.members = members
8855 Boppan 554
 
8957 Boppan 555
	def dump(self):
556
		super().dump()
9030 Boppan 557
		print(f"(Union)\n---")
8855 Boppan 558
 
8957 Boppan 559
	def emit(self, dest):
560
		# Build doxycomment
561
		doxycomment = ''
562
		doxycomment += self.comment
563
		if '@brief' not in doxycomment:
564
			doxycomment = '@brief ' + doxycomment
565
		# Build declaration
566
		declaration = f"union {self.name}" + " {};"
567
		# Emit this
568
		super().emit(dest, doxycomment, declaration)
8855 Boppan 569
 
8957 Boppan 570
class VariableNameIsMacroName:
571
	def __init__(self, name):
572
		self.name = name
8855 Boppan 573
 
8957 Boppan 574
def is_id(c):
575
	return c.isprintable() and c not in "+-/*=<>()[]{};:,|&~#`'\" \n\r\t\v"
8855 Boppan 576
 
8957 Boppan 577
def is_starts_as_id(s):
578
	return not s[0].isdigit()
579
 
580
def parse_after_macro(r):
581
	location = r.location()
582
 
583
	# Skip spaces after the "macro" keyword
584
	r.skip_spaces()
8855 Boppan 585
	# Read macro name
586
	name = ""
8957 Boppan 587
	while is_id(r.curr()) or r.curr() == '#':
588
		name += r.step()
8855 Boppan 589
	# Skip spaces after macro name
8957 Boppan 590
	r.skip_spaces()
8855 Boppan 591
	# Find all arguments
592
	args = []
593
	arg = ''
8957 Boppan 594
	while r.curr() and r.curr() != ';' and r.curr() != '{':
8855 Boppan 595
		# Collect identifier
8957 Boppan 596
		if is_id(r.curr()):
597
			arg += r.step()
8855 Boppan 598
		# Save the collected identifier
8957 Boppan 599
		elif r.curr() == ',':
8855 Boppan 600
			args.append(arg)
601
			arg = ''
8957 Boppan 602
			r.step()
8855 Boppan 603
		# Just push the '['
8957 Boppan 604
		elif r.curr() == '[':
605
			args.append(r.step())
8855 Boppan 606
		# Just push the identifier and get ']' ready to be pushed on next comma
8957 Boppan 607
		elif r.curr() == ']':
8855 Boppan 608
			args.append(arg)
8957 Boppan 609
			arg = r.step()
8855 Boppan 610
		# Just push the identifier and get '*' ready to be pushed on next comma
8957 Boppan 611
		elif r.curr() == '*':
8855 Boppan 612
			args.append(arg)
8957 Boppan 613
			arg = r.step()
8855 Boppan 614
		# Just skip whitespaces
8957 Boppan 615
		elif r.curr().isspace():
616
			r.step()
8855 Boppan 617
		# Something unexpected
618
		else:
8957 Boppan 619
			raise Exception(f"Unexpected symbol '{r.curr()}' at index #{r.i} " +
620
			                f"in the macro declaration at {location} " +
621
			                f"(line: {r.lines[r.line_idx]})\n''")
622
	# Append the last argument
8855 Boppan 623
	if arg != '':
624
		args.append(arg)
8957 Boppan 625
	# Skip t spaces after the argument list
626
	r.skip_spaces()
627
	# Get a comment if it is: read till the end of the line and get the comment from the reader
628
	while r.curr() != '':
629
		r.step()
630
	comment = r.comment
8855 Boppan 631
	# Find end of the macro
8957 Boppan 632
	prev = ''
633
	while True:
634
		if r.curr() == '}' and prev != '\\':
635
			break
636
		elif r.curr() == '':
637
			prev = ''
638
			r.nextline()
639
			continue
640
		prev = r.step()
8855 Boppan 641
	# Build the output
8957 Boppan 642
	return AsmMacro(location, name, comment, args)
8855 Boppan 643
 
8957 Boppan 644
def parse_variable(r, first_word = None):
645
	global warnings
646
	location = r.location()
8825 Boppan 647
 
8957 Boppan 648
	# Skip spaces before variable name
649
	r.skip_spaces()
650
	# Get variable name
651
	name = ""
652
	# Read it if it was not supplied
653
	if first_word == None:
654
		while is_id(r.curr()):
655
			name += r.step()
656
	# Or use the supplied one instead
657
	else:
658
		name = first_word
659
	# Check the name
660
	# If it's 0 len, that means threr's something else than an identifier at the beginning
661
	if len(name) == 0:
662
		return None
663
	# If it starts from digit or othervice illegally it's illegal
664
	if not is_starts_as_id(name):
665
		return None
8976 Boppan 666
	# Get kind of the identifier from id2kind table
667
	kind = id_get_kind(name)
8957 Boppan 668
	# If it's a keyword, that's not a variable declaration
8976 Boppan 669
	if ID_KIND_KEYWORD in kind:
8957 Boppan 670
		return None
671
	# If it's a macro name, that's not a variable declaration
8976 Boppan 672
	if ID_KIND_MACRO_NAME in kind:
8957 Boppan 673
		return VariableNameIsMacroName(name)
674
	# If it's a datatype or a structure name that's not a variable declaration: that's just a data
675
	# don't document just a data for now
8976 Boppan 676
	if ID_KIND_STRUCT_NAME in kind or ID_KIND_FASM_TYPE in kind:
8957 Boppan 677
		return None
678
	# Skip spaces before type name
679
	r.skip_spaces()
680
	# Read type name
681
	var_type = ""
682
	while is_id(r.curr()):
683
		var_type += r.step()
684
	# Check the type name
685
	if len(var_type) == 0:
686
		# If there's no type identifier after the name
687
		# maybe the name is something meaningful for the next parser
688
		# return it
689
		return name
690
	# If it starts from digit or othervice illegally it's illegal
691
	if not is_starts_as_id(var_type):
692
		return None
8976 Boppan 693
	# Get kind of type identifier
694
	type_kind = id_get_kind(var_type)
8957 Boppan 695
	# If it's a keyword, that's not a variable declaration
696
	# return the two words of the lexical structure
8976 Boppan 697
	if ID_KIND_KEYWORD in type_kind:
8957 Boppan 698
		return (name, var_type)
699
	# Skip spaces before the value
700
	r.skip_spaces()
701
	# Read the value until the comment or end of the line
702
	value = ""
703
	while r.curr() != ';' and r.curr() != '' and r.curr() != '\n':
704
		value += r.step()
705
	# Skip spaces after the value
706
	r.skip_spaces()
8961 Boppan 707
	# Read till end of the line to get a comment from the reader
708
	while r.curr() != '':
8957 Boppan 709
		r.step()
8961 Boppan 710
	# Build the result
8957 Boppan 711
	return AsmVariable(location, name, r.comment, var_type, value)
8825 Boppan 712
 
8957 Boppan 713
def parse_after_struct(r, as_union = True):
714
	global warnings
715
	location = r.location()
8825 Boppan 716
 
8957 Boppan 717
	# Skip spaces after "struct" keyword
718
	r.skip_spaces()
719
	# Read struct name
720
	name = ""
721
	while is_id(r.curr()):
722
		name += r.step()
723
	# Read till end of the line and get the comment from the reader
724
	while r.curr() != '':
725
		r.step()
726
	comment = r.comment
727
	# Get to the next line to parse struct members
728
	r.nextline()
729
	# Parse struct members
730
	members = []
731
	while True:
732
		r.skip_spaces()
733
		var = parse_variable(r)
734
		if type(var) == AsmVariable:
735
			members.append(var)
736
		elif type(var) == str:
737
			if var == 'union':
738
				# Parse the union as a struct
739
				union = parse_after_struct(r, as_union = True)
740
				members.append(union)
741
				# Skip the ends of the union
742
				r.nextline()
743
			elif r.curr() == ':':
744
				warnings += f"{r.location()}: Skept the label in the struct\n"
745
			else:
746
				raise Exception(f"Garbage in struct member at {location} (got '{var}' identifier)")
747
		elif type(var) == VariableNameIsMacroName:
748
			if var.name == 'ends':
749
				break
750
		r.nextline()
751
	# Return the result
752
	if as_union:
753
		return AsmStruct(location, name, comment, members)
754
	else:
755
		return AsmUnion(location, name, comment, members)
8825 Boppan 756
 
8963 Boppan 757
def parse_after_proc(r):
758
	# Get proc name
759
	name = r.fetch_identifier()
760
	# Next identifier after the proc name
761
	identifier = r.fetch_identifier()
762
	# Check if the id is 'stdcall' or 'c' (calling convention specifier)
763
	# and if so - save the convention and lookup the next identifier
764
	calling_convention = ''
765
	if identifier == 'stdcall' or identifier == 'c':
766
		calling_convention = identifier
767
		# If next is a comma, just skip it
768
		if r.curr() == ',':
769
			r.step()
770
		# Read the next identifier
771
		identifier = r.fetch_identifier()
772
	# Check if the id is 'uses' (used register list specifier)
773
	# and if so save the used register list
774
	used_regs = []
775
	if identifier == 'uses':
776
		# Read the registers
777
		while True:
778
			reg_name = r.fetch_identifier()
779
			if reg_name != '':
780
				used_regs.append(reg_name)
781
			else:
782
				break
783
		# If next is a comma, just skip it
784
		if r.curr() == ',':
785
			r.step()
786
		# Read the next identifier
787
		identifier = r.fetch_identifier()
788
	# Check if there are argument identifiers
789
	args = []
790
	while identifier != '':
791
		arg_name = identifier
792
		arg_type = 'arg_t'
793
		# Skip spaces after argument name
794
		r.skip_spaces()
795
		# If there's a ':' after the name - the next identifier is type
796
		if r.curr() == ':':
797
			r.step()
798
			arg_type = r.fetch_identifier()
799
		# If there's a comma - there's one more argument
800
		# else no arguments anymore
801
		if r.curr() == ',':
802
			r.step()
803
			identifier = r.fetch_identifier()
804
		else:
805
			identifier = ''
806
		args.append((arg_name, arg_type))
807
	# Get to the end of the line and get a comment from the reader
808
	while r.curr() != '':
809
		r.step()
8973 Boppan 810
	comment = r.comment
8963 Boppan 811
	# Build the element
812
	return AsmFunction(r.location(), name, comment, calling_convention, args, used_regs)
813
 
8957 Boppan 814
def get_declarations(asm_file_contents, asm_file_name):
815
	r = AsmReader(asm_file_name)
8825 Boppan 816
 
8957 Boppan 817
	while not r.no_lines():
818
		# Skip leading spaces
819
		r.skip_spaces()
820
		# Skip the line if it's starting with a comment
821
		if r.curr() == ';':
822
			r.nextline()
8825 Boppan 823
			continue
8957 Boppan 824
		# Get first word
825
		first_word = ""
826
		while is_id(r.curr()):
827
			first_word += r.step()
828
		# Match macro declaration
829
		if first_word == "macro":
830
			macro = parse_after_macro(r)
831
			elements.append(macro)
8976 Boppan 832
			id_add_kind(macro.name, ID_KIND_MACRO_NAME)
8957 Boppan 833
		# Match structure declaration
834
		elif first_word == "struct":
835
			struct = parse_after_struct(r)
836
			elements.append(struct)
8976 Boppan 837
			id_add_kind(struct.name, ID_KIND_STRUCT_NAME)
8957 Boppan 838
		# Match function definition
839
		elif first_word == "proc":
8963 Boppan 840
			proc = parse_after_proc(r)
841
			elements.append(proc)
8957 Boppan 842
		elif first_word == 'format':
843
			# Skip the format directive
844
			pass
845
		elif first_word == 'include':
846
			# Skip the include directive
847
			pass
848
		elif first_word == 'if':
849
			# Skip the conditional directive
850
			pass
851
		elif first_word == 'repeat':
852
			# Skip the repeat directive
853
			pass
854
		elif first_word == 'purge':
855
			while True:
856
				# Skip spaces after the 'purge' keyword or after the comma what separated the previous macro name
857
				r.skip_spaces()
858
				# Get the purged macro name
859
				name = ''
860
				while is_id(r.curr()):
861
					name += r.step()
862
				# Remove the purged macro from the macro names list
863
				try:
8976 Boppan 864
					id_remove_kind(name, ID_KIND_MACRO_NAME)
8957 Boppan 865
				except:
866
					pass
867
				# Skip spaces after the name
868
				r.skip_spaces()
869
				# If it's comma (',') after then that's not the last purged macro, continue purging
870
				if r.curr() == ',':
871
					r.step()
872
					continue
873
				# Here we purged all the macros should be purged
874
				break
875
		# Match label or a variable
876
		elif len(first_word) != 0:
877
			# Skip spaces after the identifier
878
			r.skip_spaces()
879
			# Match a variable
880
			var = parse_variable(r, first_word)
881
			if type(var) == AsmVariable:
882
				elements.append(var)
883
			# If it wasn't a variable but there was an identifier
884
			# Maybe that's a label and the identifier is the label name
885
			# The parse_variable returns the first found or supplied identifier
886
			# In this case it returns the first_word which is supplied
887
			# If it didn't match a type identifier after the word
888
			elif type(var) == str:
889
				name = var
890
				# Match label beginning (':' after name)
891
				if r.curr() == ':':
892
					# Get to the end of the line and get the coment from the reader
893
					while r.curr() != '':
894
						r.step()
895
					comment = r.comment
896
					# Only handle non-local labels
897
					if name[0] != '.' and name != "@@" and name != "$Revision":
8989 Boppan 898
						if '@return' in comment or '@param' in comment:
899
							element = AsmFunction(r.location(), name, comment, '', [], [])
900
						else:
901
							element = AsmLabel(r.location(), name, comment)
902
						elements.append(element)
8957 Boppan 903
				elif r.curr() == '=':
8976 Boppan 904
					# Save the identifier as a set constant
905
					id_add_kind(first_word, ID_KIND_SET_CONSTANT)
8957 Boppan 906
			elif type(var) == tuple:
907
				(word_one, word_two) = var
908
				if word_two == 'equ':
8976 Boppan 909
					# Save the identifier as an equated constant
910
					id_add_kind(word_one, ID_KIND_EQUATED_CONSTANT)
8957 Boppan 911
		r.nextline()
8825 Boppan 912
 
8966 Boppan 913
def it_neds_to_be_parsed(source_file):
8990 Boppan 914
	# If there's no symbols file saved - parse it anyway
915
	# cause we need to create the symbols file and use it
916
	# if we gonna generate proper doxygen
917
	if not os.path.isfile('asmxygen.elements.pickle'):
918
		return True
8966 Boppan 919
	dest = doxygen_src_path + '/' + source_file
920
	# If there's no the doxygen file it should be compiled to
921
	# then yes, we should compile it to doxygen
922
	if not os.path.isfile(dest):
923
		return True
924
	source_change_time = os.path.getmtime(source_file)
925
	dest_change_file = os.path.getmtime(dest)
926
	# If the source is newer than the doxygen it was compiled to
927
	# then the source should be recompiled (existing doxygen is old)
928
	if source_change_time > dest_change_file:
929
		return True
930
	return False
931
 
8834 Boppan 932
def handle_file(handled_files, asm_file_name, subdir = "."):
8990 Boppan 933
	global elements
8966 Boppan 934
	# Canonicalize the file path and get it relative to cwd
935
	cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
936
	asm_file_name = os.path.realpath(asm_file_name)
937
	asm_file_name = asm_file_name[len(cwd) + 1:]
938
	# If it's lang.inc - skip it
939
	if asm_file_name == 'lang.inc':
8967 Boppan 940
		return
941
	# If the file was handled in this execution before - skip it
942
	if asm_file_name in handled_files:
943
		return
944
	# Say that the file was handled in this execution
945
	handled_files.append(asm_file_name)
8966 Boppan 946
	# Check if the file should be parsed (if it was modified or wasn't parsed yet)
947
	should_get_declarations = True
948
	if not it_neds_to_be_parsed(asm_file_name):
949
		print(f"Skipping {asm_file_name} (already newest)")
950
		should_get_declarations = False
951
	else:
8975 Boppan 952
		print(f"Handling {asm_file_name}")
8990 Boppan 953
		# Remove elements parsed from this file before if any
954
		elements_to_remove = [x for x in elements if x.location.split(':')[0] == asm_file_name]
955
		elements = [x for x in elements if x.location.split(':')[0] != asm_file_name]
956
		# Forget types of identifiers of names of the removed elements
957
		for element in elements_to_remove:
958
			if type(element) == AsmStruct:
959
				id_remove_kind(element.name, ID_KIND_STRUCT_NAME)
960
			elif type(element) == AsmMacro:
961
				id_remove_kind(element.name, ID_KIND_MACRO_NAME)
8966 Boppan 962
	# Read the source
963
	asm_file_contents = open(asm_file_name, "r", encoding="utf-8").read()
964
	# Find includes, fix their paths and handle em recoursively
965
	includes = re.findall(r'^include (["\'])(.*)\1', asm_file_contents, flags=re.MULTILINE)
8825 Boppan 966
	for include in includes:
8957 Boppan 967
		include = include[1].replace('\\', '/');
8825 Boppan 968
		full_path = subdir + '/' + include;
8966 Boppan 969
		# If the path isn't valid, maybe that's not relative path
970
		if not os.path.isfile(full_path):
971
			full_path = include
8967 Boppan 972
		new_subdir = full_path.rsplit('/', 1)[0]
973
		handle_file(handled_files, full_path, new_subdir)
8966 Boppan 974
	# Only collect declarations from the file if it wasn't parsed before
8975 Boppan 975
	if should_get_declarations and not clean_generated_stuff:
8966 Boppan 976
		get_declarations(asm_file_contents, asm_file_name)
8825 Boppan 977
 
9399 Boppan 978
# Dict where an identifier is assicoated with a string
979
# The string contains characters specifying flags
980
# Available flags:
981
#  k - Keyword
982
#  m - Macro name
983
#  t - fasm data Type name (db, rq, etc.)
984
#  s - Struct type name
985
#  e - equated constant (name equ value)
986
#  = - set constants (name = value)
987
ID_KIND_KEYWORD = 'k'
988
ID_KIND_MACRO_NAME = 'm'
989
ID_KIND_FASM_TYPE = 't'
990
ID_KIND_STRUCT_NAME = 's'
991
ID_KIND_EQUATED_CONSTANT = 'e'
992
ID_KIND_SET_CONSTANT = '='
993
id2kind = {}
994
 
995
for keyword in keywords:
996
	id_add_kind(keyword, ID_KIND_KEYWORD)
997
 
998
for fasm_type in fasm_types:
999
	id_add_kind(fasm_type, ID_KIND_FASM_TYPE)
1000
 
1001
# Warning list
1002
warnings = ""
1003
 
1004
# Parse arguments
1005
parser = argparse.ArgumentParser()
1006
parser.add_argument("-o", help="Doxygen output folder")
1007
parser.add_argument("--clean", help="Remove generated files", action="store_true")
1008
parser.add_argument("--dump", help="Dump all defined symbols", action="store_true")
1009
parser.add_argument("--stats", help="Print symbol stats", action="store_true")
1010
parser.add_argument("--nowarn", help="Do not write warnings file", action="store_true")
1011
parser.add_argument("--noemit", help="Do not emit doxygen files (for testing)", action="store_true")
1012
args = parser.parse_args()
1013
doxygen_src_path = args.o if args.o else 'docs/doxygen'
1014
clean_generated_stuff = args.clean
1015
dump_symbols = args.dump
1016
print_stats = args.stats
1017
enable_warnings = not args.nowarn
1018
noemit = args.noemit
1019
 
1020
# Variables, functions, labels, macros, structure types
1021
elements = []
1022
 
1023
created_files = []
1024
 
8825 Boppan 1025
kernel_files = []
1026
 
8990 Boppan 1027
# Load remembered list of symbols
1028
if os.path.isfile('asmxygen.elements.pickle'):
1029
	print('Reading existing dump of symbols')
1030
	(elements, id2kind) = pickle.load(open('asmxygen.elements.pickle', 'rb'))
1031
 
8834 Boppan 1032
handle_file(kernel_files, "./kernel.asm");
8825 Boppan 1033
 
8834 Boppan 1034
if dump_symbols:
9033 Boppan 1035
	stdout = sys.stdout
1036
	sys.stdout = open('asmxygen.dump.txt', 'w', encoding = 'utf-8')
8957 Boppan 1037
	for asm_element in elements:
1038
		asm_element.dump()
9033 Boppan 1039
	sys.stdout = stdout
8825 Boppan 1040
 
8967 Boppan 1041
if clean_generated_stuff:
1042
	kernel_files_set = set(kernel_files)
1043
	for file in kernel_files:
1044
		doxygen_file = f"{doxygen_src_path}/{file}"
1045
		if (os.path.isfile(doxygen_file)):
1046
			print(f"Removing {file}... ", end = '')
1047
			os.remove(doxygen_file)
1048
			print("Done.")
8976 Boppan 1049
elif not noemit:
8967 Boppan 1050
	print(f"Writing doumented sources to {doxygen_src_path}")
8834 Boppan 1051
 
8967 Boppan 1052
	i = 0
8990 Boppan 1053
	new_elements = [x for x in elements if x.new]
1054
	for element in new_elements:
1055
		print(f"[{i + 1}/{len(new_elements)}] Emitting {element.name} from {element.location}")
8967 Boppan 1056
		element.emit(doxygen_src_path)
1057
		i += 1
8855 Boppan 1058
 
8990 Boppan 1059
	print(f"Writing dump of symbols to asmxygen.elements.pickle")
1060
 
1061
	# Now when the new elements already was written, there's no new elements anymore
1062
	for element in elements:
1063
		element.new = False
1064
	pickle.dump((elements, id2kind), open('asmxygen.elements.pickle', 'wb'))
1065
 
8982 Boppan 1066
if print_stats:
1067
	var_count = 0
1068
	mac_count = 0
1069
	lab_count = 0
1070
	fun_count = 0
1071
	uni_count = 0
1072
	str_count = 0
1073
	for element in elements:
1074
		if type(element) == AsmVariable:
1075
			var_count += 1
1076
		elif type(element) == AsmMacro:
1077
			mac_count += 1
1078
		elif type(element) == AsmLabel:
1079
			lab_count += 1
1080
		elif type(element) == AsmFunction:
1081
			fun_count += 1
1082
		elif type(element) == AsmUnion:
1083
			uni_count += 1
1084
		elif type(element) == AsmStruct:
1085
			str_count += 1
1086
	print(f'Parsed variable count: {var_count}')
1087
	print(f'Parsed macro count: {mac_count}')
1088
	print(f'Parsed label count: {lab_count}')
1089
	print(f'Parsed function count: {fun_count}')
1090
	print(f'Parsed union type count: {uni_count}')
1091
	print(f'Parsed structure type count: {str_count}')
1092
 
8855 Boppan 1093
if enable_warnings:
1094
	open('asmxygen.txt', "w", encoding = "utf-8").write(warnings)