Added DGEN to ISO and create file associatons.
[kolibrios.git] / kernel / trunk / asmxygen.py
blobf3b6b535477c1650d7257fa9d0a8a0e8f4d9a3a2
1 import re
2 import os
3 import argparse
4 import sys
5 import pickle
6 import hashlib
7 import difflib
9 # fasm keywords
10 keywords = [
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",
158 fasm_types = [
159 "db", "rb",
160 "dw", "rw",
161 "dd", "rd",
162 "dp", "rp",
163 "df", "rf",
164 "dq", "rq",
165 "dt", "rt",
166 "du",
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]
188 else:
189 return ''
192 class LegacyAsmReader:
193 def __init__(self, file):
194 self.file = file
195 self.lines = open(file, "r", encoding="utf-8").readlines()
196 self.line_idx = 0
197 self.i = 0
199 def currline(self):
200 return self.lines[self.line_idx]
202 def curr(self):
203 try:
204 return self.lines[self.line_idx][self.i]
205 except:
206 return ''
208 def step(self):
209 c = self.curr()
210 self.i += 1
211 # Wrap the line if '\\' followed by whitespaces and/or comment
212 while self.curr() == '\\':
213 i_of_backslash = self.i
214 self.i += 1
215 while self.curr().isspace():
216 self.i += 1
217 if self.curr() == ';' or self.curr() == '':
218 self.line_idx += 1
219 self.i = 0
220 else:
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
224 break
225 return c
227 def nextline(self):
228 c = self.curr()
229 while c != '':
230 c = self.step()
231 self.line_idx += 1
232 self.i = 0
234 def no_lines(self):
235 if self.line_idx >= len(self.lines):
236 return True
237 return False
239 def location(self):
240 return f"{self.file}:{self.line_idx + 1}"
242 def skip_spaces(self):
243 while self.curr().isspace():
244 self.step()
247 class AsmReaderRecognizingStrings(LegacyAsmReader):
248 def __init__(self, file):
249 super().__init__(file)
250 self.in_string = None
251 self.should_recognize_strings = True
253 def step(self):
254 c = super().step()
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:
260 self.in_string = c
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
266 return c
269 class AsmReaderReadingComments(AsmReaderRecognizingStrings):
270 def __init__(self, file):
271 super().__init__(file)
272 self.status = dict()
273 self.status_reset()
274 self.comment = ''
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
297 self.curr() == ';'):
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()
307 def step(self):
308 # Get to the next character
309 c = super().step()
310 # Update status of the line according to the next character
311 self.update_status()
312 return c
314 def nextline(self):
315 prev_line = self.currline()
316 super().nextline()
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 "):
326 self.comment = ''
327 # Reset the line status (now it's the status of the new line)
328 self.status_reset()
329 # Set new status for this line according to the
330 # first character in the line
331 self.update_status()
334 class AsmReaderFetchingIdentifiers(AsmReaderReadingComments):
335 def __init__(self, file):
336 super().__init__(file)
338 def fetch_identifier(self):
339 self.skip_spaces()
340 result = ''
341 while is_id(self.curr()):
342 result += self.step()
343 return result
346 class AsmReader(AsmReaderFetchingIdentifiers):
347 def __init__(self, file):
348 super().__init__(file)
351 def append_file(full_path, contents):
352 if debug_mode:
353 if full_path not in output_files:
354 output_files[full_path] = ""
355 output_files[full_path] += contents
356 else:
357 f = open(full_path, "a")
358 f.write(contents)
359 f.close()
362 class AsmElement:
363 def __init__(self, location, name, comment):
364 global warnings
366 # If the element was constructed during this execution then
367 # the element is new
368 self.new = True
369 self.location = location
370 self.file = self.location.split(':')[0].replace('\\', '/')
371 self.line = self.location.split(':')[1]
372 self.name = name
373 self.comment = comment
375 if self.comment == '':
376 warnings += f'{self.location}: Undocumented element\n'
378 def dump(self):
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:
385 return
387 global warnings
388 # Redefine default declaration
389 if declaration == '':
390 declaration = f'#define {self.name}'
391 # Check doxycomment
392 if not doxycomment.endswith('\n'):
393 doxycomment += '\n'
394 if doxycomment.split('@brief ')[1][0].islower():
395 warnings += (f"{self.location}: Brief comment starting from " +
396 "lowercase\n")
397 # Build contents to emit
398 contents = ''
399 contents += '/**\n'
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")
404 contents += '*/\n'
405 contents += declaration
406 contents += '\n\n'
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):
413 os.remove(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)
425 self.type = type
426 self.init = init
428 def dump(self):
429 super().dump()
430 print(f"(Variable)\n---")
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
438 doxycomment += (f"@par Initial value\n" +
439 f"{self.init}\n")
440 # Build the declaration
441 name = self.name.replace(".", "_")
442 var_type = self.type.replace(".", "_")
443 declaration = f"{var_type} {name};"
444 # Emit this
445 super().emit(dest, doxycomment, declaration)
448 class AsmFunction(AsmElement):
449 def __init__(self, location, name, comment, calling_convention,
450 args, used_regs):
451 super().__init__(location, name, comment)
452 self.calling_convention = calling_convention
453 self.args = args
454 self.used_regs = used_regs
456 def dump(self):
457 super().dump()
458 print(f"(Function)\n---")
460 def emit(self, dest):
461 # Build doxycomment specific for the variable
462 doxycomment = ''
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:
469 i = 0
470 while '@param' in self.comment[i:]:
471 i = self.comment.index('@param', i)
472 # Skip '@param'
473 i += len('@param')
474 # Skip spaces after '@param'
475 while self.comment[i].isspace():
476 i += 1
477 # Get the parameter name
478 name = ''
479 while is_id(self.comment[i]):
480 name += self.comment[i]
481 i += 1
482 # Save the parameter
483 self.args.append((name, 'arg_t'))
484 # Build the arg list for declaration
485 arg_list = '('
486 if len(self.args) > 0:
487 argc = 0
488 for arg in self.args:
489 if argc != 0:
490 arg_list += ", "
491 arg_list += f"{arg[1]} {arg[0]}"
492 argc += 1
493 arg_list += ')'
494 # Build the declaration
495 name = self.name.replace(".", "_")
496 declaration = f"void {name}{arg_list};"
497 # Emit this
498 super().emit(dest, doxycomment, declaration)
501 class AsmLabel(AsmElement):
502 def __init__(self, location, name, comment):
503 super().__init__(location, name, comment)
505 def dump(self):
506 super().dump()
507 print(f"(Label)\n---")
509 def emit(self, dest):
510 # Build doxycomment specific for the variable
511 doxycomment = ''
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};"
518 # Emit this
519 super().emit(dest, doxycomment, declaration)
522 class AsmMacro(AsmElement):
523 def __init__(self, location, name, comment, args):
524 super().__init__(location, name, comment)
525 self.args = args
527 def dump(self):
528 super().dump()
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
535 arg_list = ""
536 if len(args) > 0:
537 arg_list += '('
538 argc = 0
539 for arg in args:
540 if argc != 0:
541 arg_list += ", "
542 arg_list += arg
543 argc += 1
544 arg_list += ')'
545 # Build doxycomment
546 doxycomment = ''
547 doxycomment += self.comment
548 if '@brief' not in doxycomment:
549 doxycomment = '@brief ' + doxycomment
550 # Build declaration
551 declaration = f"#define {self.name}{arg_list}"
552 # Emit this
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
561 def dump(self):
562 super().dump()
563 print(f"(Struct)\n---")
565 def emit(self, dest):
566 # Build doxycomment
567 doxycomment = ''
568 doxycomment += self.comment
569 if '@brief' not in doxycomment:
570 doxycomment = '@brief ' + doxycomment
571 doxycomment += '\n'
572 # Build declaration
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')
578 declaration += '};'
579 # Emit this
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
588 def dump(self):
589 super().dump()
590 print(f"(Union)\n---")
592 def emit(self, dest):
593 # Build doxycomment
594 doxycomment = ''
595 doxycomment += self.comment
596 if '@brief' not in doxycomment:
597 doxycomment = '@brief ' + doxycomment
598 # Build declaration
599 declaration = f"union {self.name}" + " {};"
600 # Emit this
601 super().emit(dest, doxycomment, declaration)
604 class VariableNameIsMacroName:
605 def __init__(self, name):
606 self.name = name
609 def is_id(c):
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
621 r.skip_spaces()
622 # Read macro name
623 name = ""
624 while is_id(r.curr()) or r.curr() == '#':
625 name += r.step()
626 # Skip spaces after macro name
627 r.skip_spaces()
628 # Find all arguments
629 args = []
630 arg = ''
631 while r.curr() and r.curr() != ';' and r.curr() != '{':
632 # Collect identifier
633 if is_id(r.curr()):
634 arg += r.step()
635 # Save the collected identifier
636 elif r.curr() == ',':
637 args.append(arg)
638 arg = ''
639 r.step()
640 # Just push the '['
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() == ']':
645 args.append(arg)
646 arg = r.step()
647 # Just push the identifier and get '*' ready to be pushed on next comma
648 elif r.curr() == '*':
649 args.append(arg)
650 arg = r.step()
651 # Just skip whitespaces
652 elif r.curr().isspace():
653 r.step()
654 # Something unexpected
655 else:
656 raise Exception(f"Unexpected symbol '{r.curr()}' " +
657 f"at index #{r.i} in the macro declaration " +
658 f"at {location} " +
659 f"(line: {r.lines[r.line_idx]})\n''")
660 # Append the last argument
661 if arg != '':
662 args.append(arg)
663 # Skip t spaces after the argument list
664 r.skip_spaces()
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() != '':
668 r.step()
669 comment = r.comment
670 # Find end of the macro
671 prev = ''
672 while True:
673 if r.curr() == '}' and prev != '\\':
674 break
675 elif r.curr() == '':
676 prev = ''
677 r.nextline()
678 continue
679 prev = r.step()
680 # Build the output
681 return AsmMacro(location, name, comment, args)
684 def parse_variable(r, first_word=None):
685 global warnings
686 location = r.location()
688 # Skip spaces before variable name
689 r.skip_spaces()
690 # Get variable name
691 name = ""
692 # Read it if it was not supplied
693 if first_word is None:
694 while is_id(r.curr()):
695 name += r.step()
696 # Or use the supplied one instead
697 else:
698 name = first_word
699 # Check the name
700 # If it's 0 len, that means threr's something else than an
701 # identifier at the beginning
702 if len(name) == 0:
703 return None
704 # If it starts from digit or othervice illegally it's illegal
705 if not is_starts_as_id(name):
706 return None
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:
711 return None
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:
719 return None
720 # Skip spaces before type name
721 r.skip_spaces()
722 # Read type name
723 var_type = ""
724 while is_id(r.curr()):
725 var_type += r.step()
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
730 # return it
731 return name
732 # If it starts from digit or othervice illegally it's illegal
733 if not is_starts_as_id(var_type):
734 return None
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
742 r.skip_spaces()
743 # Read the value until the comment or end of the line
744 value = ""
745 while r.curr() != ';' and r.curr() != '' and r.curr() != '\n':
746 value += r.step()
747 # Skip spaces after the value
748 r.skip_spaces()
749 # Read till end of the line to get a comment from the reader
750 while r.curr() != '':
751 r.step()
752 # Build the result
753 return AsmVariable(location, name, r.comment, var_type, value)
756 def parse_after_struct(r, as_union=True):
757 global warnings
758 location = r.location()
760 # Skip spaces after "struct" keyword
761 r.skip_spaces()
762 # Read struct name
763 name = ""
764 while is_id(r.curr()):
765 name += r.step()
766 # Read till end of the line and get the comment from the reader
767 while r.curr() != '':
768 r.step()
769 comment = r.comment
770 # Get to the next line to parse struct members
771 r.nextline()
772 # Parse struct members
773 members = []
774 while True:
775 r.skip_spaces()
776 var = parse_variable(r)
777 if type(var) == AsmVariable:
778 members.append(var)
779 elif type(var) == str:
780 if var == 'union':
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
785 r.nextline()
786 elif r.curr() == ':':
787 warnings += f"{r.location()}: Skept the label in the struct\n"
788 else:
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':
793 break
794 r.nextline()
795 # Return the result
796 if as_union:
797 return AsmStruct(location, name, comment, members)
798 else:
799 return AsmUnion(location, name, comment, members)
802 def parse_after_proc(r):
803 # Get proc name
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
813 if r.curr() == ',':
814 r.step()
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
819 used_regs = []
820 if identifier == 'uses':
821 # Read the registers
822 while True:
823 reg_name = r.fetch_identifier()
824 if reg_name != '':
825 used_regs.append(reg_name)
826 else:
827 break
828 # If next is a comma, just skip it
829 if r.curr() == ',':
830 r.step()
831 # Read the next identifier
832 identifier = r.fetch_identifier()
833 # Check if there are argument identifiers
834 args = []
835 while identifier != '':
836 arg_name = identifier
837 arg_type = 'arg_t'
838 # Skip spaces after argument name
839 r.skip_spaces()
840 # If there's a ':' after the name - the next identifier is type
841 if r.curr() == ':':
842 r.step()
843 arg_type = r.fetch_identifier()
844 # If there's a comma - there's one more argument
845 # else no arguments anymore
846 if r.curr() == ',':
847 r.step()
848 identifier = r.fetch_identifier()
849 else:
850 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() != '':
854 r.step()
855 comment = r.comment
856 # Build the element
857 return AsmFunction(r.location(), name, comment, calling_convention,
858 args, used_regs)
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
866 r.skip_spaces()
867 # Skip the line if it's starting with a comment
868 if r.curr() == ';':
869 r.nextline()
870 continue
871 # Get first word
872 first_word = ""
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
891 pass
892 elif first_word == 'include':
893 # Skip the include directive
894 pass
895 elif first_word == 'if':
896 # Skip the conditional directive
897 pass
898 elif first_word == 'repeat':
899 # Skip the repeat directive
900 pass
901 elif first_word == 'purge':
902 while True:
903 # Skip spaces after the 'purge' keyword or after
904 # the comma what separated the previous macro name
905 r.skip_spaces()
906 # Get the purged macro name
907 name = ''
908 while is_id(r.curr()):
909 name += r.step()
910 # Remove the purged macro from the macro names list
911 try:
912 id_remove_kind(name, ID_KIND_MACRO_NAME)
913 except:
914 pass
915 # Skip spaces after the name
916 r.skip_spaces()
917 # If it's comma (',') after then that's not the last purged
918 # macro, continue purging
919 if r.curr() == ',':
920 r.step()
921 continue
922 # Here we purged all the macros should be purged
923 break
924 # Match label or a variable
925 elif len(first_word) != 0:
926 # Skip spaces after the identifier
927 r.skip_spaces()
928 # Match a variable
929 var = parse_variable(r, first_word)
930 if type(var) == AsmVariable:
931 elements.append(var)
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:
938 name = var
939 # Match label beginning (':' after name)
940 if r.curr() == ':':
941 # Get to the end of the line and
942 # get the coment from the reader
943 while r.curr() != '':
944 r.step()
945 comment = r.comment
946 # Only handle non-local labels
947 if name[0] != '.' and name != "@@" and name != "$Revision":
948 # Treate the label as function if there's @return or
949 # @param in its comment. Othervice 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,
953 '', [], [])
954 else:
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)
965 r.nextline()
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'):
973 return True
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):
978 return True
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:
984 return True
985 return False
988 def handle_file(handled_files, asm_file_name, subdir="."):
989 global elements
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':
996 return
997 # If the file was handled in this execution before - skip it
998 if asm_file_name in handled_files:
999 return
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
1008 else:
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
1014 elements = [
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)
1023 # Read the source
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,
1027 flags=re.MULTILINE)
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):
1033 full_path = include
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
1046 # Available flags:
1047 # k - Keyword
1048 # m - Macro name
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 = '='
1059 id2kind = {}
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)
1067 # Warning list
1068 warnings = ""
1070 # Parameters
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
1078 print_stats = False
1079 # Do not write warnings file: --nowarn
1080 enable_warnings = True
1082 # Parse arguments
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
1113 elements = []
1114 created_files = []
1115 kernel_files = []
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)
1123 pickle_file.close()
1125 handle_file(kernel_files, "./kernel.asm")
1127 if dump_symbols:
1128 stdout = sys.stdout
1129 sys.stdout = open('asmxygen.dump.txt', 'w', encoding='utf-8')
1130 for asm_element in elements:
1131 asm_element.dump()
1132 sys.stdout = stdout
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)
1141 print("Done.")
1142 elif not noemit:
1143 print(f"Writing doumented sources to {doxygen_src_path}")
1145 i = 0
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)
1151 i += 1
1153 print(f"Writing dump of symbols to asmxygen.elements.pickle")
1155 # Now when the new elements already was written, there's no new
1156 # elements anymore
1157 for element in elements:
1158 element.new = False
1159 pickle_file = open('asmxygen.elements.pickle', 'wb')
1160 pickle.dump((elements, id2kind), pickle_file)
1161 pickle_file.close()
1163 if print_stats:
1164 var_count = 0
1165 mac_count = 0
1166 lab_count = 0
1167 fun_count = 0
1168 uni_count = 0
1169 str_count = 0
1170 for element in elements:
1171 if type(element) == AsmVariable:
1172 var_count += 1
1173 elif type(element) == AsmMacro:
1174 mac_count += 1
1175 elif type(element) == AsmLabel:
1176 lab_count += 1
1177 elif type(element) == AsmFunction:
1178 fun_count += 1
1179 elif type(element) == AsmUnion:
1180 uni_count += 1
1181 elif type(element) == AsmStruct:
1182 str_count += 1
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}')
1190 if enable_warnings:
1191 open('asmxygen.txt', "w", encoding="utf-8").write(warnings)
1193 if debug_mode:
1194 hash_per_file = ""
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)
1200 print("NEW")
1201 else:
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))
1206 else:
1207 print("SUCCESS")