11 "align", "equ", "org", "while", "load", "store", "times", "repeat",
12 "display", "err", "assert", "if", "aaa", "aad", "aam", "aas", "adc",
13 "add", "addpd", "addps", "addsd", "addss", "addsubpd", "addsubps", "adox",
14 "aesdeclast", "aesenc", "aesenclast", "aesimc", "aeskeygenassist", "and",
15 "andnpd", "andnps", "andpd", "andps", "arpl", "bextr", "blendpd",
16 "blendvpd", "blendvps", "blsi", "blsmsk", "blsr", "bndcl", "bndcn",
17 "bndldx", "bndmk", "bndmov", "bndstx", "bound", "bsf", "bsr", "bswap",
18 "btc", "btr", "bts", "bzhi", "call", "cbw", "cdq", "cdqe", "clac", "clc",
19 "cldemote", "clflush", "clflushopt", "cli", "clts", "clwb", "cmc", "cmova",
20 "cmovb", "cmovbe", "cmovc", "cmove", "cmovg", "cmovge", "cmovl", "cmovle",
21 "cmovnae", "cmovnb", "cmovnbe", "cmovnc", "cmovne", "cmovng", "cmovnge",
22 "cmovnle", "cmovno", "cmovnp", "cmovns", "cmovnz", "cmovo", "cmovp",
23 "cmovpo", "cmovs", "cmovz", "cmp", "cmppd", "cmpps", "cmps", "cmpsb",
24 "cmpsd", "cmpsq", "cmpss", "cmpsw", "cmpxchg", "cmpxchg16b", "cmpxchg8b",
25 "comiss", "cpuid", "cqo", "crc32", "cvtdq2pd", "cvtdq2ps", "cvtpd2dq",
26 "cvtpd2ps", "cvtpi2pd", "cvtpi2ps", "cvtps2dq", "cvtps2pd", "cvtps2pi",
27 "cvtsd2ss", "cvtsi2sd", "cvtsi2ss", "cvtss2sd", "cvtss2si", "cvttpd2dq",
28 "cvttps2dq", "cvttps2pi", "cvttsd2si", "cvttss2si", "cwd", "cwde", "daa",
29 "dec", "div", "divpd", "divps", "divsd", "divss", "dppd", "dpps", "emms",
30 "extractps", "f2xm1", "fabs", "fadd", "faddp", "fbld", "fbstp", "fchs",
31 "fcmova", "fcmovae", "fcmovb", "fcmovbe", "fcmovc", "fcmove", "fcmovg",
32 "fcmovl", "fcmovle", "fcmovna", "fcmovnae", "fcmovnb", "fcmovnbe",
33 "fcmovne", "fcmovng", "fcmovnge", "fcmovnl", "fcmovnle", "fcmovno",
34 "fcmovns", "fcmovnz", "fcmovo", "fcmovp", "fcmovpe", "fcmovpo", "fcmovs",
35 "fcom", "fcomi", "fcomip", "fcomp", "fcompp", "fcos", "fdecstp", "fdiv",
36 "fdivr", "fdivrp", "ffree", "fiadd", "ficom", "ficomp", "fidiv", "fidivr",
37 "fimul", "fincstp", "finit", "fist", "fistp", "fisttp", "fisub", "fisubr",
38 "fld1", "fldcw", "fldenv", "fldl2e", "fldl2t", "fldlg2", "fldln2", "fldpi",
39 "fmul", "fmulp", "fnclex", "fninit", "fnop", "fnsave", "fnstcw", "fnstenv",
40 "fpatan", "fprem", "fprem1", "fptan", "frndint", "frstor", "fsave",
41 "fsin", "fsincos", "fsqrt", "fst", "fstcw", "fstenv", "fstp", "fstsw",
42 "fsubp", "fsubr", "fsubrp", "ftst", "fucom", "fucomi", "fucomip", "fucomp",
43 "fwait", "fxam", "fxch", "fxrstor", "fxsave", "fxtract", "fyl2x",
44 "gf2p8affineinvqb", "gf2p8affineqb", "gf2p8mulb", "haddpd", "haddps",
45 "hsubpd", "hsubps", "idiv", "imul", "in", "inc", "ins", "insb", "insd",
46 "insw", "int", "int1", "int3", "into", "invd", "invlpg", "invpcid", "iret",
47 "jmp", "ja", "jae", "jb", "jbe", "jc", "jcxz", "jecxz", "je", "jg", "jge",
48 "jle", "jna", "jnae", "jnb", "jnbe", "jnc", "jne", "jng", "jnge", "jnl",
49 "jno", "jnp", "jns", "jnz", "jo", "jp", "jpe", "jpo", "js", "jz", "kaddb",
50 "kaddq", "kaddw", "kandb", "kandd", "kandnb", "kandnd", "kandnq", "kandnw",
51 "kandw", "kmovb", "kmovd", "kmovq", "kmovw", "knotb", "knotd", "knotq",
52 "korb", "kord", "korq", "kortestb", "kortestd", "kortestq", "kortestw",
53 "kshiftlb", "kshiftld", "kshiftlq", "kshiftlw", "kshiftrb", "kshiftrd",
54 "kshiftrw", "ktestb", "ktestd", "ktestq", "ktestw", "kunpckbw", "kunpckdq",
55 "kxnorb", "kxnord", "kxnorq", "kxnorw", "kxorb", "kxord", "kxorq", "kxorw",
56 "lar", "lddqu", "ldmxcsr", "lds", "lea", "leave", "les", "lfence", "lfs",
57 "lgs", "lidt", "lldt", "lmsw", "lock", "lods", "lodsb", "lodsd", "lodsq",
58 "loop", "loopa", "loopae", "loopb", "loopbe", "loopc", "loope", "loopg",
59 "loopl", "loople", "loopna", "loopnae", "loopnb", "loopnbe", "loopnc",
60 "loopng", "loopnge", "loopnl", "loopnle", "loopno", "loopnp", "loopns",
61 "loopo", "loopp", "looppe", "looppo", "loops", "loopz", "lsl", "lss",
62 "lzcnt", "maskmovdqu", "maskmovq", "maxpd", "maxps", "maxsd", "maxss",
63 "minpd", "minps", "minsd", "minss", "monitor", "mov", "movapd", "movaps",
64 "movd", "movddup", "movdir64b", "movdiri", "movdq2q", "movdqa", "movdqu",
65 "movhpd", "movhps", "movlhps", "movlpd", "movlps", "movmskpd", "movmskps",
66 "movntdqa", "movnti", "movntpd", "movntps", "movntq", "movq", "movq",
67 "movs", "movsb", "movsd", "movsd", "movshdup", "movsldup", "movsq",
68 "movsw", "movsx", "movsxd", "movupd", "movups", "movzx", "mpsadbw", "mul",
69 "mulps", "mulsd", "mulss", "mulx", "mwait", "neg", "nop", "not", "or",
70 "orps", "out", "outs", "outsb", "outsd", "outsw", "pabsb", "pabsd",
71 "pabsw", "packssdw", "packsswb", "packusdw", "packuswb", "paddb", "paddd",
72 "paddsb", "paddsw", "paddusb", "paddusw", "paddw", "palignr", "pand",
73 "pause", "pavgb", "pavgw", "pblendvb", "pblendw", "pclmulqdq", "pcmpeqb",
74 "pcmpeqq", "pcmpeqw", "pcmpestri", "pcmpestrm", "pcmpgtb", "pcmpgtd",
75 "pcmpgtw", "pcmpistri", "pcmpistrm", "pdep", "pext", "pextrb", "pextrd",
76 "pextrw", "phaddd", "phaddsw", "phaddw", "phminposuw", "phsubd", "phsubsw",
77 "pinsrb", "pinsrd", "pinsrq", "pinsrw", "pmaddubsw", "pmaddwd", "pmaxsb",
78 "pmaxsq", "pmaxsw", "pmaxub", "pmaxud", "pmaxuq", "pmaxuw", "pminsb",
79 "pminsq", "pminsw", "pminub", "pminud", "pminuq", "pminuw", "pmovmskb",
80 "pmovzx", "pmuldq", "pmulhrsw", "pmulhuw", "pmulhw", "pmulld", "pmullq",
81 "pmuludq", "pop", "popa", "popad", "popcnt", "popf", "popfd", "popfq",
82 "prefetchw", "prefetchh", "psadbw", "pshufb", "pshufd", "pshufhw",
83 "pshufw", "psignb", "psignd", "psignw", "pslld", "pslldq", "psllq",
84 "psrad", "psraq", "psraw", "psrld", "psrldq", "psrlq", "psrlw", "psubb",
85 "psubq", "psubsb", "psubsw", "psubusb", "psubusw", "psubw", "ptest",
86 "punpckhbw", "punpckhdq", "punpckhqdq", "punpckhwd", "punpcklbw",
87 "punpcklqdq", "punpcklwd", "push", "pushw", "pushd", "pusha", "pushad",
88 "pushfd", "pushfq", "pxor", "rcl", "rcpps", "rcpss", "rcr", "rdfsbase",
89 "rdmsr", "rdpid", "rdpkru", "rdpmc", "rdrand", "rdseed", "rdtsc", "rdtscp",
90 "repe", "repne", "repnz", "repz", "ret", "rol", "ror", "rorx", "roundpd",
91 "roundsd", "roundss", "rsm", "rsqrtps", "rsqrtss", "sahf", "sal", "sar",
92 "sbb", "scas", "scasb", "scasd", "scasw", "seta", "setae", "setb", "setbe",
93 "sete", "setg", "setge", "setl", "setle", "setna", "setnae", "setnb",
94 "setnc", "setne", "setng", "setnge", "setnl", "setnle", "setno", "setnp",
95 "setnz", "seto", "setp", "setpe", "setpo", "sets", "setz", "sfence",
96 "sha1msg1", "sha1msg2", "sha1nexte", "sha1rnds4", "sha256msg1",
97 "sha256rnds2", "shl", "shld", "shlx", "shr", "shrd", "shrx", "shufpd",
98 "sidt", "sldt", "smsw", "sqrtpd", "sqrtps", "sqrtsd", "sqrtss", "stac",
99 "std", "sti", "stmxcsr", "stos", "stosb", "stosd", "stosq", "stosw", "str",
100 "subpd", "subps", "subsd", "subss", "swapgs", "syscall", "sysenter",
101 "sysret", "test", "tpause", "tzcnt", "ucomisd", "ucomiss", "ud",
102 "umwait", "unpckhpd", "unpckhps", "unpcklpd", "unpcklps", "valignd",
103 "vblendmpd", "vblendmps", "vbroadcast", "vcompresspd", "vcompressps",
104 "vcvtpd2udq", "vcvtpd2uqq", "vcvtph2ps", "vcvtps2ph", "vcvtps2qq",
105 "vcvtps2uqq", "vcvtqq2pd", "vcvtqq2ps", "vcvtsd2usi", "vcvtss2usi",
106 "vcvttpd2udq", "vcvttpd2uqq", "vcvttps2qq", "vcvttps2udq", "vcvttps2uqq",
107 "vcvttss2usi", "vcvtudq2pd", "vcvtudq2ps", "vcvtuqq2pd", "vcvtuqq2ps",
108 "vcvtusi2ss", "vdbpsadbw", "verr", "verw", "vexpandpd", "vexpandps",
109 "vextractf32x4", "vextractf32x8", "vextractf64x2", "vextractf64x4",
110 "vextracti32x4", "vextracti32x8", "vextracti64x2", "vextracti64x4",
111 "vfixupimmps", "vfixupimmsd", "vfixupimmss", "vfmadd132pd", "vfmadd132ps",
112 "vfmadd132ss", "vfmadd213pd", "vfmadd213ps", "vfmadd213sd", "vfmadd213ss",
113 "vfmadd231ps", "vfmadd231sd", "vfmadd231ss", "vfmaddsub132pd",
114 "vfmaddsub213pd", "vfmaddsub213ps", "vfmaddsub231pd", "vfmaddsub231ps",
115 "vfmsub132ps", "vfmsub132sd", "vfmsub132ss", "vfmsub213pd", "vfmsub213ps",
116 "vfmsub213ss", "vfmsub231pd", "vfmsub231ps", "vfmsub231sd", "vfmsub231ss",
117 "vfmsubadd132ps", "vfmsubadd213pd", "vfmsubadd213ps", "vfmsubadd231pd",
118 "vfnmadd132pd", "vfnmadd132ps", "vfnmadd132sd", "vfnmadd132ss",
119 "vfnmadd213ps", "vfnmadd213sd", "vfnmadd213ss", "vfnmadd231pd",
120 "vfnmadd231sd", "vfnmadd231ss", "vfnmsub132pd", "vfnmsub132ps",
121 "vfnmsub132ss", "vfnmsub213pd", "vfnmsub213ps", "vfnmsub213sd",
122 "vfnmsub231pd", "vfnmsub231ps", "vfnmsub231sd", "vfnmsub231ss",
123 "vfpclassps", "vfpclasssd", "vfpclassss", "vgatherdpd", "vgatherdpd",
124 "vgatherdps", "vgatherqpd", "vgatherqpd", "vgatherqps", "vgatherqps",
125 "vgetexpps", "vgetexpsd", "vgetexpss", "vgetmantpd", "vgetmantps",
126 "vgetmantss", "vinsertf128", "vinsertf32x4", "vinsertf32x8",
127 "vinsertf64x4", "vinserti128", "vinserti32x4", "vinserti32x8",
128 "vinserti64x4", "vmaskmov", "vmovdqa32", "vmovdqa64", "vmovdqu16",
129 "vmovdqu64", "vmovdqu8", "vpblendd", "vpblendmb", "vpblendmd", "vpblendmq",
130 "vpbroadcast", "vpbroadcastb", "vpbroadcastd", "vpbroadcastm",
131 "vpbroadcastw", "vpcmpb", "vpcmpd", "vpcmpq", "vpcmpub", "vpcmpud",
132 "vpcmpuw", "vpcmpw", "vpcompressd", "vpcompressq", "vpconflictd",
133 "vperm2f128", "vperm2i128", "vpermb", "vpermd", "vpermi2b", "vpermi2d",
134 "vpermi2ps", "vpermi2q", "vpermi2w", "vpermilpd", "vpermilps", "vpermpd",
135 "vpermq", "vpermt2b", "vpermt2d", "vpermt2pd", "vpermt2ps", "vpermt2q",
136 "vpermw", "vpexpandd", "vpexpandq", "vpgatherdd", "vpgatherdd",
137 "vpgatherdq", "vpgatherqd", "vpgatherqd", "vpgatherqq", "vpgatherqq",
138 "vplzcntq", "vpmadd52huq", "vpmadd52luq", "vpmaskmov", "vpmovb2m",
139 "vpmovdb", "vpmovdw", "vpmovm2b", "vpmovm2d", "vpmovm2q", "vpmovm2w",
140 "vpmovqb", "vpmovqd", "vpmovqw", "vpmovsdb", "vpmovsdw", "vpmovsqb",
141 "vpmovsqw", "vpmovswb", "vpmovusdb", "vpmovusdw", "vpmovusqb", "vpmovusqd",
142 "vpmovuswb", "vpmovw2m", "vpmovwb", "vpmultishiftqb", "vprold", "vprolq",
143 "vprolvq", "vprord", "vprorq", "vprorvd", "vprorvq", "vpscatterdd",
144 "vpscatterqd", "vpscatterqq", "vpsllvd", "vpsllvq", "vpsllvw", "vpsravd",
145 "vpsravw", "vpsrlvd", "vpsrlvq", "vpsrlvw", "vpternlogd", "vpternlogq",
146 "vptestmd", "vptestmq", "vptestmw", "vptestnmb", "vptestnmd", "vptestnmq",
147 "vrangepd", "vrangeps", "vrangesd", "vrangess", "vrcp14pd", "vrcp14ps",
148 "vrcp14ss", "vreducepd", "vreduceps", "vreducesd", "vreducess",
149 "vrndscaleps", "vrndscalesd", "vrndscaless", "vrsqrt14pd", "vrsqrt14ps",
150 "vrsqrt14ss", "vscalefpd", "vscalefps", "vscalefsd", "vscalefss",
151 "vscatterdps", "vscatterqpd", "vscatterqps", "vshuff32x4", "vshuff64x2",
152 "vshufi64x2", "vtestpd", "vtestps", "vzeroall", "vzeroupper", "wait",
153 "wrfsbase", "wrgsbase", "wrmsr", "wrpkru", "xabort", "xacquire", "xadd",
154 "xchg", "xend", "xgetbv", "xlat", "xlatb", "xor", "xorpd", "xorps",
155 "xrstor", "xrstors", "xsave", "xsavec", "xsaveopt", "xsaves", "xsetbv",
170 # Add kind flag to identifier in id2kind
171 def id_add_kind(identifier
, kind
):
172 if identifier
not in id2kind
:
173 id2kind
[identifier
] = ''
174 id2kind
[identifier
] += kind
177 # Remove kind flag of identifier in id2kind
178 def id_remove_kind(identifier
, kind
):
179 if identifier
in id2kind
:
180 if kind
in id2kind
[identifier
]:
181 id2kind
[identifier
] = id2kind
[identifier
].replace(kind
, '')
184 # Get kind of an identifier
185 def id_get_kind(identifier
):
186 if identifier
in id2kind
:
187 return id2kind
[identifier
]
192 class LegacyAsmReader
:
193 def __init__(self
, file):
195 self
.lines
= open(file, "r", encoding
="utf-8").readlines()
200 return self
.lines
[self
.line_idx
]
204 return self
.lines
[self
.line_idx
][self
.i
]
211 # Wrap the line if '\\' followed by whitespaces and/or comment
212 while self
.curr() == '\\':
213 i_of_backslash
= self
.i
215 while self
.curr().isspace():
217 if self
.curr() == ';' or self
.curr() == '':
221 # There's something other than a comment after the backslash
222 # So don't interpret the backslash as a line wrap
223 self
.i
= i_of_backslash
235 if self
.line_idx
>= len(self
.lines
):
240 return f
"{self.file}:{self.line_idx + 1}"
242 def skip_spaces(self
):
243 while self
.curr().isspace():
247 class AsmReaderRecognizingStrings(LegacyAsmReader
):
248 def __init__(self
, file):
249 super().__init
__(file)
250 self
.in_string
= None
251 self
.should_recognize_strings
= True
255 if self
.should_recognize_strings
and (c
== '"' or c
== "'"):
256 # If just now we was at the double or single quotation mark
257 # and we aren't in a string yet then say
258 # "we are in a string openned with this quotation mark now"
259 if self
.in_string
is None:
261 # If just now we was at the double or single quotation mark
262 # and we are in the string entered with the same quotation mark
263 # then say "we aren't in a string anymore"
264 elif self
.in_string
== c
:
265 self
.in_string
= None
269 class AsmReaderReadingComments(AsmReaderRecognizingStrings
):
270 def __init__(self
, file):
271 super().__init
__(file)
276 def status_reset(self
):
277 # If the line has non-comment code
278 self
.status_has_code
= False
279 # If the line has a comment at the end
280 self
.status_has_comment
= False
281 # Let it recognize strings further, we are definitely out of a comment
282 self
.should_recognize_strings
= True
284 def status_set_has_comment(self
):
285 self
.status_has_comment
= True
286 # Don't let it recognize strings cause we are in a comment now
287 self
.should_recognize_strings
= False
289 def status_set_has_code(self
):
290 self
.status_has_code
= True
292 def update_status(self
):
293 # If we aren't in a comment and we aren't in a string -
294 # say we are now in a comment if ';' met
295 if (not self
.status_has_comment
and
296 not self
.in_string
and
298 self
.status_set_has_comment()
299 # Else if we are in a comment - collect the comment
300 elif self
.status_has_comment
:
301 self
.comment
+= self
.curr()
302 # Else if there's some non-whitespace character out of a comment
303 # then the line has code
304 elif not self
.status_has_comment
and not self
.curr().isspace():
305 self
.status_set_has_code()
308 # Get to the next character
310 # Update status of the line according to the next character
315 prev_line
= self
.currline()
317 # If the line we leave was not a comment-only line
318 # then forget the collected comment
319 # Otherwise the collected comment should be complemented by
320 # comment from next line in step()
321 if self
.status_has_code
:
322 # But we should preserve comment for the next line
323 # If previous line set align (cause many functions re documented
324 # right before align set, not before their labels)
325 if not prev_line
.startswith("align "):
327 # Reset the line status (now it's the status of the new line)
329 # Set new status for this line according to the
330 # first character in the line
334 class AsmReaderFetchingIdentifiers(AsmReaderReadingComments
):
335 def __init__(self
, file):
336 super().__init
__(file)
338 def fetch_identifier(self
):
341 while is_id(self
.curr()):
342 result
+= self
.step()
346 class AsmReader(AsmReaderFetchingIdentifiers
):
347 def __init__(self
, file):
348 super().__init
__(file)
351 def append_file(full_path
, contents
):
353 if full_path
not in output_files
:
354 output_files
[full_path
] = ""
355 output_files
[full_path
] += contents
357 f
= open(full_path
, "a")
363 def __init__(self
, location
, name
, comment
):
366 # If the element was constructed during this execution then
369 self
.location
= location
370 self
.file = self
.location
.split(':')[0].replace('\\', '/')
371 self
.line
= self
.location
.split(':')[1]
373 self
.comment
= comment
375 if self
.comment
== '':
376 warnings
+= f
'{self.location}: Undocumented element\n'
379 print(f
"\n{self.location}: {self.name}")
380 print(f
"{self.comment}")
382 def emit(self
, dest
, doxycomment
='', declaration
=''):
383 # Do not emit anything if the symbol is marked as hidden in its comment
384 if '@dont_give_a_doxygen' in self
.comment
:
388 # Redefine default declaration
389 if declaration
== '':
390 declaration
= f
'#define {self.name}'
392 if not doxycomment
.endswith('\n'):
394 if doxycomment
.split('@brief ')[1][0].islower():
395 warnings
+= (f
"{self.location}: Brief comment starting from " +
397 # Build contents to emit
400 contents
+= doxycomment
401 contents
+= (f
"@par Source\n" +
402 f
"<a href='{link_root}/{self.file}" +
403 f
"#line-{self.line}'>{self.file}:{self.line}</a>\n")
405 contents
+= declaration
407 # Get path to file to emit this
408 full_path
= dest
+ '/' + self
.file
409 # Remove the file on first access if it was
410 # created by previous generation
411 if full_path
not in created_files
:
412 if os
.path
.isfile(full_path
):
414 created_files
.append(full_path
)
415 # Create directories need for the file
416 os
.makedirs(os
.path
.dirname(full_path
), exist_ok
=True)
417 contents
= ''.join([i
if ord(i
) < 128 else '?' for i
in contents
])
419 append_file(full_path
, contents
)
422 class AsmVariable(AsmElement
):
423 def __init__(self
, location
, name
, comment
, type, init
):
424 super().__init
__(location
, name
, comment
)
430 print(f
"(Variable)\n---")
432 def emit(self
, dest
):
433 # Build doxycomment specific for the variable
435 doxycomment
+= self
.comment
436 if '@brief' not in doxycomment
:
437 doxycomment
= '@brief ' + doxycomment
438 doxycomment
+= (f
"@par Initial value\n" +
440 # Build the declaration
441 name
= self
.name
.replace(".", "_")
442 var_type
= self
.type.replace(".", "_")
443 declaration
= f
"{var_type} {name};"
445 super().emit(dest
, doxycomment
, declaration
)
448 class AsmFunction(AsmElement
):
449 def __init__(self
, location
, name
, comment
, calling_convention
,
451 super().__init
__(location
, name
, comment
)
452 self
.calling_convention
= calling_convention
454 self
.used_regs
= used_regs
458 print(f
"(Function)\n---")
460 def emit(self
, dest
):
461 # Build doxycomment specific for the variable
463 doxycomment
+= self
.comment
464 if '@brief' not in doxycomment
:
465 doxycomment
= '@brief ' + doxycomment
466 # If there was no arguments, maybe that's just a label
467 # then parse parameters from its comment
468 if len(self
.args
) == 0 and '@param' in self
.comment
:
470 while '@param' in self
.comment
[i
:]:
471 i
= self
.comment
.index('@param', i
)
474 # Skip spaces after '@param'
475 while self
.comment
[i
].isspace():
477 # Get the parameter name
479 while is_id(self
.comment
[i
]):
480 name
+= self
.comment
[i
]
483 self
.args
.append((name
, 'arg_t'))
484 # Build the arg list for declaration
486 if len(self
.args
) > 0:
488 for arg
in self
.args
:
491 arg_list
+= f
"{arg[1]} {arg[0]}"
494 # Build the declaration
495 name
= self
.name
.replace(".", "_")
496 declaration
= f
"void {name}{arg_list};"
498 super().emit(dest
, doxycomment
, declaration
)
501 class AsmLabel(AsmElement
):
502 def __init__(self
, location
, name
, comment
):
503 super().__init
__(location
, name
, comment
)
507 print(f
"(Label)\n---")
509 def emit(self
, dest
):
510 # Build doxycomment specific for the variable
512 doxycomment
+= self
.comment
513 if '@brief' not in doxycomment
:
514 doxycomment
= '@brief ' + doxycomment
515 # Build the declaration
516 name
= self
.name
.replace(".", "_")
517 declaration
= f
"label {name};"
519 super().emit(dest
, doxycomment
, declaration
)
522 class AsmMacro(AsmElement
):
523 def __init__(self
, location
, name
, comment
, args
):
524 super().__init
__(location
, name
, comment
)
529 print(f
"(Macro)\n---")
531 def emit(self
, dest
):
532 # Construct arg list without '['s, ']'s and '*'s
533 args
= [arg
for arg
in self
.args
if arg
not in "[]*"]
534 # Construct C-like arg list
547 doxycomment
+= self
.comment
548 if '@brief' not in doxycomment
:
549 doxycomment
= '@brief ' + doxycomment
551 declaration
= f
"#define {self.name}{arg_list}"
553 super().emit(dest
, doxycomment
, declaration
)
556 class AsmStruct(AsmElement
):
557 def __init__(self
, location
, name
, comment
, members
):
558 super().__init
__(location
, name
, comment
)
559 self
.members
= members
563 print(f
"(Struct)\n---")
565 def emit(self
, dest
):
568 doxycomment
+= self
.comment
569 if '@brief' not in doxycomment
:
570 doxycomment
= '@brief ' + doxycomment
573 declaration
= f
"struct {self.name}" + " {\n"
574 for member
in self
.members
:
575 if type(member
) == AsmVariable
:
576 declaration
+= (f
'\t{member.type} {member.name}; ' +
577 f
'/**< {member.comment} */\n')
580 super().emit(dest
, doxycomment
, declaration
)
583 class AsmUnion(AsmElement
):
584 def __init__(self
, location
, name
, comment
, members
):
585 super().__init
__(location
, name
, comment
)
586 self
.members
= members
590 print(f
"(Union)\n---")
592 def emit(self
, dest
):
595 doxycomment
+= self
.comment
596 if '@brief' not in doxycomment
:
597 doxycomment
= '@brief ' + doxycomment
599 declaration
= f
"union {self.name}" + " {};"
601 super().emit(dest
, doxycomment
, declaration
)
604 class VariableNameIsMacroName
:
605 def __init__(self
, name
):
610 return c
.isprintable() and c
not in "+-/*=<>()[]{};:,|&~#`'\" \n\r\t\v"
613 def is_starts_as_id(s
):
614 return not s
[0].isdigit()
617 def parse_after_macro(r
):
618 location
= r
.location()
620 # Skip spaces after the "macro" keyword
624 while is_id(r
.curr()) or r
.curr() == '#':
626 # Skip spaces after macro name
631 while r
.curr() and r
.curr() != ';' and r
.curr() != '{':
635 # Save the collected identifier
636 elif r
.curr() == ',':
641 elif r
.curr() == '[':
642 args
.append(r
.step())
643 # Just push the identifier and get ']' ready to be pushed on next comma
644 elif r
.curr() == ']':
647 # Just push the identifier and get '*' ready to be pushed on next comma
648 elif r
.curr() == '*':
651 # Just skip whitespaces
652 elif r
.curr().isspace():
654 # Something unexpected
656 raise Exception(f
"Unexpected symbol '{r.curr()}' " +
657 f
"at index #{r.i} in the macro declaration " +
659 f
"(line: {r.lines[r.line_idx]})\n''")
660 # Append the last argument
663 # Skip t spaces after the argument list
665 # Get a comment if it is: read till the end of the line and
666 # get the comment from the reader
667 while r
.curr() != '':
670 # Find end of the macro
673 if r
.curr() == '}' and prev
!= '\\':
681 return AsmMacro(location
, name
, comment
, args
)
684 def parse_variable(r
, first_word
=None):
686 location
= r
.location()
688 # Skip spaces before variable name
692 # Read it if it was not supplied
693 if first_word
is None:
694 while is_id(r
.curr()):
696 # Or use the supplied one instead
700 # If it's 0 len, that means threr's something else than an
701 # identifier at the beginning
704 # If it starts from digit or othervice illegally it's illegal
705 if not is_starts_as_id(name
):
707 # Get kind of the identifier from id2kind table
708 kind
= id_get_kind(name
)
709 # If it's a keyword, that's not a variable declaration
710 if ID_KIND_KEYWORD
in kind
:
712 # If it's a macro name, that's not a variable declaration
713 if ID_KIND_MACRO_NAME
in kind
:
714 return VariableNameIsMacroName(name
)
715 # If it's a datatype or a structure name that's not a
716 # variable declaration: that's just a data
717 # don't document just a data for now
718 if ID_KIND_STRUCT_NAME
in kind
or ID_KIND_FASM_TYPE
in kind
:
720 # Skip spaces before type name
724 while is_id(r
.curr()):
726 # Check the type name
727 if len(var_type
) == 0:
728 # If there's no type identifier after the name
729 # maybe the name is something meaningful for the next parser
732 # If it starts from digit or othervice illegally it's illegal
733 if not is_starts_as_id(var_type
):
735 # Get kind of type identifier
736 type_kind
= id_get_kind(var_type
)
737 # If it's a keyword, that's not a variable declaration
738 # return the two words of the lexical structure
739 if ID_KIND_KEYWORD
in type_kind
:
740 return (name
, var_type
)
741 # Skip spaces before the value
743 # Read the value until the comment or end of the line
745 while r
.curr() != ';' and r
.curr() != '' and r
.curr() != '\n':
747 # Skip spaces after the value
749 # Read till end of the line to get a comment from the reader
750 while r
.curr() != '':
753 return AsmVariable(location
, name
, r
.comment
, var_type
, value
)
756 def parse_after_struct(r
, as_union
=True):
758 location
= r
.location()
760 # Skip spaces after "struct" keyword
764 while is_id(r
.curr()):
766 # Read till end of the line and get the comment from the reader
767 while r
.curr() != '':
770 # Get to the next line to parse struct members
772 # Parse struct members
776 var
= parse_variable(r
)
777 if type(var
) == AsmVariable
:
779 elif type(var
) == str:
781 # Parse the union as a struct
782 union
= parse_after_struct(r
, as_union
=True)
783 members
.append(union
)
784 # Skip the ends of the union
786 elif r
.curr() == ':':
787 warnings
+= f
"{r.location()}: Skept the label in the struct\n"
789 raise Exception(f
"Garbage in struct member at {location} " +
790 f
" (got '{var}' identifier)")
791 elif type(var
) == VariableNameIsMacroName
:
792 if var
.name
== 'ends':
797 return AsmStruct(location
, name
, comment
, members
)
799 return AsmUnion(location
, name
, comment
, members
)
802 def parse_after_proc(r
):
804 name
= r
.fetch_identifier()
805 # Next identifier after the proc name
806 identifier
= r
.fetch_identifier()
807 # Check if the id is 'stdcall' or 'c' (calling convention specifier)
808 # and if so - save the convention and lookup the next identifier
809 calling_convention
= ''
810 if identifier
== 'stdcall' or identifier
== 'c':
811 calling_convention
= identifier
812 # If next is a comma, just skip it
815 # Read the next identifier
816 identifier
= r
.fetch_identifier()
817 # Check if the id is 'uses' (used register list specifier)
818 # and if so save the used register list
820 if identifier
== 'uses':
823 reg_name
= r
.fetch_identifier()
825 used_regs
.append(reg_name
)
828 # If next is a comma, just skip it
831 # Read the next identifier
832 identifier
= r
.fetch_identifier()
833 # Check if there are argument identifiers
835 while identifier
!= '':
836 arg_name
= identifier
838 # Skip spaces after argument name
840 # If there's a ':' after the name - the next identifier is type
843 arg_type
= r
.fetch_identifier()
844 # If there's a comma - there's one more argument
845 # else no arguments anymore
848 identifier
= r
.fetch_identifier()
851 args
.append((arg_name
, arg_type
))
852 # Get to the end of the line and get a comment from the reader
853 while r
.curr() != '':
857 return AsmFunction(r
.location(), name
, comment
, calling_convention
,
861 def get_declarations(asm_file_contents
, asm_file_name
):
862 r
= AsmReader(asm_file_name
)
864 while not r
.no_lines():
865 # Skip leading spaces
867 # Skip the line if it's starting with a comment
873 while is_id(r
.curr()):
874 first_word
+= r
.step()
875 # Match macro declaration
876 if first_word
== "macro":
877 macro
= parse_after_macro(r
)
878 elements
.append(macro
)
879 id_add_kind(macro
.name
, ID_KIND_MACRO_NAME
)
880 # Match structure declaration
881 elif first_word
== "struct":
882 struct
= parse_after_struct(r
)
883 elements
.append(struct
)
884 id_add_kind(struct
.name
, ID_KIND_STRUCT_NAME
)
885 # Match function definition
886 elif first_word
== "proc":
887 proc
= parse_after_proc(r
)
888 elements
.append(proc
)
889 elif first_word
== 'format':
890 # Skip the format directive
892 elif first_word
== 'include':
893 # Skip the include directive
895 elif first_word
== 'if':
896 # Skip the conditional directive
898 elif first_word
== 'repeat':
899 # Skip the repeat directive
901 elif first_word
== 'purge':
903 # Skip spaces after the 'purge' keyword or after
904 # the comma what separated the previous macro name
906 # Get the purged macro name
908 while is_id(r
.curr()):
910 # Remove the purged macro from the macro names list
912 id_remove_kind(name
, ID_KIND_MACRO_NAME
)
915 # Skip spaces after the name
917 # If it's comma (',') after then that's not the last purged
918 # macro, continue purging
922 # Here we purged all the macros should be purged
924 # Match label or a variable
925 elif len(first_word
) != 0:
926 # Skip spaces after the identifier
929 var
= parse_variable(r
, first_word
)
930 if type(var
) == AsmVariable
:
932 # If it wasn't a variable but there was an identifier
933 # Maybe that's a label and the identifier is the label name
934 # The parse_variable returns the first found or supplied identifier
935 # In this case it returns the first_word which is supplied
936 # If it didn't match a type identifier after the word
937 elif type(var
) == str:
939 # Match label beginning (':' after name)
941 # Get to the end of the line and
942 # get the coment from the reader
943 while r
.curr() != '':
946 # Only handle non-local labels
947 if name
[0] != '.' and name
!= "@@":
948 # Treat the label as function if there's @return or
949 # @param in its comment. Otherwise it's just a variable
950 # with type `label` in generated doxygen C
951 if '@return' in comment
or '@param' in comment
:
952 element
= AsmFunction(r
.location(), name
, comment
,
955 element
= AsmLabel(r
.location(), name
, comment
)
956 elements
.append(element
)
957 elif r
.curr() == '=':
958 # Save the identifier as a set constant
959 id_add_kind(first_word
, ID_KIND_SET_CONSTANT
)
960 elif type(var
) == tuple:
961 (word_one
, word_two
) = var
962 if word_two
== 'equ':
963 # Save the identifier as an equated constant
964 id_add_kind(word_one
, ID_KIND_EQUATED_CONSTANT
)
968 def it_neds_to_be_parsed(source_file
):
969 # If there's no symbols file saved - parse it anyway
970 # cause we need to create the symbols file and use it
971 # if we gonna generate proper doxygen
972 if not os
.path
.isfile('asmxygen.elements.pickle'):
974 dest
= doxygen_src_path
+ '/' + source_file
975 # If there's no the doxygen file it should be compiled to
976 # then yes, we should compile it to doxygen
977 if not os
.path
.isfile(dest
):
979 source_change_time
= os
.path
.getmtime(source_file
)
980 dest_change_file
= os
.path
.getmtime(dest
)
981 # If the source is newer than the doxygen it was compiled to
982 # then the source should be recompiled (existing doxygen is old)
983 if source_change_time
> dest_change_file
:
988 def handle_file(handled_files
, asm_file_name
, subdir
="."):
990 # Canonicalize the file path and get it relative to cwd
991 cwd
= os
.path
.abspath(os
.path
.dirname(sys
.argv
[0]))
992 asm_file_name
= os
.path
.realpath(asm_file_name
)
993 asm_file_name
= asm_file_name
[len(cwd
) + 1:]
994 # If it's lang.inc - skip it
995 if asm_file_name
== 'lang.inc':
997 # If the file was handled in this execution before - skip it
998 if asm_file_name
in handled_files
:
1000 # Say that the file was handled in this execution
1001 handled_files
.append(asm_file_name
)
1002 # Check if the file should be parsed
1003 # (if it was modified or wasn't parsed yet)
1004 should_get_declarations
= True
1005 if not it_neds_to_be_parsed(asm_file_name
):
1006 print(f
"Skipping {asm_file_name} (already newest)")
1007 should_get_declarations
= False
1009 print(f
"Handling {asm_file_name}")
1010 # Remove elements parsed from this file before if any
1011 elements_to_remove
= [
1012 x
for x
in elements
if x
.location
.split(':')[0] == asm_file_name
1015 x
for x
in elements
if x
.location
.split(':')[0] != asm_file_name
1017 # Forget types of identifiers of names of the removed elements
1018 for element
in elements_to_remove
:
1019 if type(element
) == AsmStruct
:
1020 id_remove_kind(element
.name
, ID_KIND_STRUCT_NAME
)
1021 elif type(element
) == AsmMacro
:
1022 id_remove_kind(element
.name
, ID_KIND_MACRO_NAME
)
1024 asm_file_contents
= open(asm_file_name
, "r", encoding
="utf-8").read()
1025 # Find includes, fix their paths and handle em recoursively
1026 includes
= re
.findall(r
'^include (["\'])(.*)\
1', asm_file_contents,
1028 for include in includes:
1029 include = include[1].replace('\\', '/')
1030 full_path = subdir + '/' + include
1031 # If the path isn't valid
, maybe that
's not relative path
1032 if not os.path.isfile(full_path):
1034 new_subdir = full_path.rsplit('/', 1)[0]
1035 handle_file(handled_files, full_path, new_subdir)
1036 # Only collect declarations from the file if it wasn't parsed before
1037 if should_get_declarations
and not clean_generated_stuff
:
1038 get_declarations(asm_file_contents
, asm_file_name
)
1040 if __name__
== "__main__":
1041 link_root
= "http://websvn.kolibrios.org/filedetails.php"
1042 link_root
+= "?repname=Kolibri+OS&path=/kernel/trunk"
1044 # Dict where an identifier is assicoated with a string
1045 # The string contains characters specifying flags
1049 # t - fasm data Type name (db, rq, etc.)
1050 # s - Struct type name
1051 # e - equated constant (name equ value)
1052 # = - set constants (name = value)
1053 ID_KIND_KEYWORD
= 'k'
1054 ID_KIND_MACRO_NAME
= 'm'
1055 ID_KIND_FASM_TYPE
= 't'
1056 ID_KIND_STRUCT_NAME
= 's'
1057 ID_KIND_EQUATED_CONSTANT
= 'e'
1058 ID_KIND_SET_CONSTANT
= '='
1061 for keyword
in keywords
:
1062 id_add_kind(keyword
, ID_KIND_KEYWORD
)
1064 for fasm_type
in fasm_types
:
1065 id_add_kind(fasm_type
, ID_KIND_FASM_TYPE
)
1071 # Path to doxygen folder to make doxygen files in: -o <path>
1072 doxygen_src_path
= 'docs/doxygen'
1073 # Remove generated doxygen files: --clean
1074 clean_generated_stuff
= False
1075 # Dump all defined symbols: --dump
1076 dump_symbols
= False
1077 # Print symbol stats: --stats
1079 # Do not write warnings file: --nowarn
1080 enable_warnings
= True
1083 parser
= argparse
.ArgumentParser()
1084 parser
.add_argument("-o", help="Doxygen output folder")
1085 parser
.add_argument("--clean",
1086 help="Remove generated files",
1087 action
="store_true")
1088 parser
.add_argument("--dump",
1089 help="Dump all defined symbols",
1090 action
="store_true")
1091 parser
.add_argument("--stats",
1092 help="Print symbol stats",
1093 action
="store_true")
1094 parser
.add_argument("--nowarn",
1095 help="Do not write warnings file",
1096 action
="store_true")
1097 parser
.add_argument("--noemit",
1098 help="Do not emit doxygen files (for testing)",
1099 action
="store_true")
1100 parser
.add_argument("--debug",
1101 help="Show hashes of files (for testing)",
1102 action
="store_true")
1103 args
= parser
.parse_args()
1104 doxygen_src_path
= args
.o
if args
.o
else 'docs/doxygen'
1105 clean_generated_stuff
= args
.clean
1106 dump_symbols
= args
.dump
1107 print_stats
= args
.stats
1108 enable_warnings
= not args
.nowarn
1109 noemit
= args
.noemit
1110 debug_mode
= args
.debug
1112 # Variables, functions, labels, macros, structure types
1116 output_files
= {} # If --debug then all the files are written here
1118 # Load remembered list of symbols
1119 if os
.path
.isfile('asmxygen.elements.pickle'):
1120 print('Reading existing dump of symbols')
1121 pickle_file
= open('asmxygen.elements.pickle', 'rb')
1122 (elements
, id2kind
) = pickle
.load(pickle_file
)
1125 handle_file(kernel_files
, "./kernel.asm")
1129 sys
.stdout
= open('asmxygen.dump.txt', 'w', encoding
='utf-8')
1130 for asm_element
in elements
:
1134 if clean_generated_stuff
:
1135 kernel_files_set
= set(kernel_files
)
1136 for file in kernel_files
:
1137 doxygen_file
= f
"{doxygen_src_path}/{file}"
1138 if (os
.path
.isfile(doxygen_file
)):
1139 print(f
"Removing {file}... ", end
='')
1140 os
.remove(doxygen_file
)
1143 print(f
"Writing doumented sources to {doxygen_src_path}")
1146 new_elements
= [x
for x
in elements
if x
.new
]
1147 for element
in new_elements
:
1148 counter
= f
"[{i + 1}/{len(new_elements)}]"
1149 print(f
"{counter} Emitting {element.name} from {element.location}")
1150 element
.emit(doxygen_src_path
)
1153 print(f
"Writing dump of symbols to asmxygen.elements.pickle")
1155 # Now when the new elements already was written, there's no new
1157 for element
in elements
:
1159 pickle_file
= open('asmxygen.elements.pickle', 'wb')
1160 pickle
.dump((elements
, id2kind
), pickle_file
)
1170 for element
in elements
:
1171 if type(element
) == AsmVariable
:
1173 elif type(element
) == AsmMacro
:
1175 elif type(element
) == AsmLabel
:
1177 elif type(element
) == AsmFunction
:
1179 elif type(element
) == AsmUnion
:
1181 elif type(element
) == AsmStruct
:
1183 print(f
'Parsed variable count: {var_count}')
1184 print(f
'Parsed macro count: {mac_count}')
1185 print(f
'Parsed label count: {lab_count}')
1186 print(f
'Parsed function count: {fun_count}')
1187 print(f
'Parsed union type count: {uni_count}')
1188 print(f
'Parsed structure type count: {str_count}')
1191 open('asmxygen.txt', "w", encoding
="utf-8").write(warnings
)
1195 for file in output_files
:
1196 h
= hashlib
.sha1(bytes(output_files
[file], "ascii")).hexdigest()
1197 hash_per_file
+= f
"{file}: {h}\n"
1198 if not os
.path
.exists("asmxygen_hash_per_file.txt"):
1199 open("asmxygen_hash_per_file.txt", "w").write(hash_per_file
)
1202 reference_hash_per_file
= open("asmxygen_hash_per_file.txt").read()
1203 if reference_hash_per_file
!= hash_per_file
:
1204 diffs
= difflib
.ndiff(reference_hash_per_file
, hash_per_file
)
1205 print(''.join(diffs
))