Subversion Repositories Kolibri OS

Rev

Rev 9403 | Rev 9408 | 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
9402 Boppan 6
import hashlib
7
import difflib
8825 Boppan 8
 
8957 Boppan 9
# fasm keywords
10
keywords = [
9398 Boppan 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",
8957 Boppan 156
]
157
 
158
fasm_types = [
9407 Boppan 159
    "db", "rb",
160
    "dw", "rw",
161
    "dd", "rd",
162
    "dp", "rp",
163
    "df", "rf",
164
    "dq", "rq",
165
    "dt", "rt",
166
    "du",
8957 Boppan 167
]
168
 
8976 Boppan 169
# Add kind flag to identifier in id2kind
170
def id_add_kind(identifier, kind):
9407 Boppan 171
    if identifier not in id2kind:
172
        id2kind[identifier] = ''
173
    id2kind[identifier] += kind
8976 Boppan 174
 
175
# Remove kind flag of identifier in id2kind
176
def id_remove_kind(identifier, kind):
9407 Boppan 177
    if identifier in id2kind:
178
        if kind in id2kind[identifier]:
179
            id2kind[identifier] = id2kind[identifier].replace(kind, '')
8976 Boppan 180
 
181
# Get kind of an identifier
182
def id_get_kind(identifier):
9407 Boppan 183
    if identifier in id2kind:
184
        return id2kind[identifier]
185
    else:
186
        return ''
8976 Boppan 187
 
8957 Boppan 188
class LegacyAsmReader:
9407 Boppan 189
    def __init__(self, file):
190
        self.file = file
191
        self.lines = open(file, "r", encoding="utf-8").readlines()
192
        self.line_idx = 0
193
        self.i = 0
8957 Boppan 194
 
9407 Boppan 195
    def currline(self):
196
        return self.lines[self.line_idx]
9403 Boppan 197
 
9407 Boppan 198
    def curr(self):
199
        try: return self.lines[self.line_idx][self.i]
200
        except: return ''
8957 Boppan 201
 
9407 Boppan 202
    def step(self):
203
        c = self.curr()
204
        self.i += 1
205
        # Wrap the line if '\\' followed by whitespaces and/or comment
206
        while self.curr() == '\\':
207
            i_of_backslash = self.i
208
            self.i += 1
209
            while self.curr().isspace():
210
                self.i += 1
211
            if self.curr() == ';' or self.curr() == '':
212
                self.line_idx += 1
213
                self.i = 0
214
            else:
215
                # There's something other than a comment after the backslash
216
                # So don't interpret the backslash as a line wrap
217
                self.i = i_of_backslash
218
                break
219
        return c
8957 Boppan 220
 
9407 Boppan 221
    def nextline(self):
222
        c = self.curr()
223
        while c != '':
224
            c = self.step()
225
        self.line_idx += 1
226
        self.i = 0
8957 Boppan 227
 
9407 Boppan 228
    def no_lines(self):
229
        if self.line_idx >= len(self.lines):
230
            return True
231
        return False
8957 Boppan 232
 
9407 Boppan 233
    def location(self):
234
        return f"{self.file}:{self.line_idx + 1}"
8957 Boppan 235
 
9407 Boppan 236
    def skip_spaces(self):
237
        while self.curr().isspace():
238
            self.step()
8957 Boppan 239
 
240
class AsmReaderRecognizingStrings(LegacyAsmReader):
9407 Boppan 241
    def __init__(self, file):
242
        super().__init__(file)
243
        self.in_string = None
244
        self.should_recognize_strings = True
8957 Boppan 245
 
9407 Boppan 246
    def step(self):
247
        c = super().step()
248
        if self.should_recognize_strings and (c == '"' or c == "'"):
249
            # If just now we was at the double or single quotation mark
250
            # and we aren't in a string yet
251
            # then say "we are in a string openned with this quotation mark now"
252
            if self.in_string == None:
253
                self.in_string = c
254
            # If just now we was at the double or single quotation mark
255
            # and we are in the string entered with the same quotation mark
256
            # then say "we aren't in a string anymore"
257
            elif self.in_string == c:
258
                self.in_string = None
259
        return c
8957 Boppan 260
 
261
class AsmReaderReadingComments(AsmReaderRecognizingStrings):
9407 Boppan 262
    def __init__(self, file):
263
        super().__init__(file)
264
        self.status = dict()
265
        self.status_reset()
266
        self.comment = ''
8957 Boppan 267
 
9407 Boppan 268
    def status_reset(self):
269
        # If the line has non-comment code
270
        self.status_has_code = False
271
        # If the line has a comment at the end
272
        self.status_has_comment = False
273
        # Let it recognize strings further, we are definitely out of a comment
274
        self.should_recognize_strings = True
8957 Boppan 275
 
9407 Boppan 276
    def status_set_has_comment(self):
277
        self.status_has_comment = True
278
        # Don't let it recognize strings cause we are in a comment now
279
        self.should_recognize_strings = False
8957 Boppan 280
 
9407 Boppan 281
    def status_set_has_code(self):
282
        self.status_has_code = True
8957 Boppan 283
 
9407 Boppan 284
    def update_status(self):
285
        # If we aren't in a comment and we aren't in a string - say we are now in a comment if ';' met
286
        if not self.status_has_comment and not self.in_string and self.curr() == ';':
287
            self.status_set_has_comment()
288
        # Else if we are in a comment - collect the comment
289
        elif self.status_has_comment:
290
            self.comment += self.curr()
291
        # Else if there's some non-whitespace character out of a comment
292
        # then the line has code
293
        elif not self.status_has_comment and not self.curr().isspace():
294
            self.status_set_has_code()
8957 Boppan 295
 
9407 Boppan 296
    def step(self):
297
        # Get to the next character
298
        c = super().step()
299
        # Update status of the line according to the next character
300
        self.update_status()
301
        return c
8957 Boppan 302
 
9407 Boppan 303
    def nextline(self):
304
        prev_line = self.currline()
305
        super().nextline()
306
        # If the line we leave was not a comment-only line
307
        # then forget the collected comment
308
        # Otherwise the collected comment should be complemented by comment from next line in step()
309
        if self.status_has_code:
310
            # But we should preserve comment for the next line
311
            # If previous line set align (cause many functions re documented
312
            # right before align set, not before their labels)
313
            if not prev_line.startswith("align "):
314
                self.comment = ''
315
        # Reset the line status (now it's the status of the new line)
316
        self.status_reset()
317
        # Set new status for this line according to the first character in the line
318
        self.update_status()
8957 Boppan 319
 
8963 Boppan 320
class AsmReaderFetchingIdentifiers(AsmReaderReadingComments):
9407 Boppan 321
    def __init__(self, file):
322
        super().__init__(file)
8957 Boppan 323
 
9407 Boppan 324
    def fetch_identifier(self):
325
        self.skip_spaces()
326
        result = ''
327
        while is_id(self.curr()):
328
            result += self.step()
329
        return result
8963 Boppan 330
 
331
class AsmReader(AsmReaderFetchingIdentifiers):
9407 Boppan 332
    def __init__(self, file):
333
        super().__init__(file)
8963 Boppan 334
 
9402 Boppan 335
def append_file(full_path, contents):
9407 Boppan 336
    if debug_mode:
337
        if full_path not in output_files:
338
            output_files[full_path] = ""
339
        output_files[full_path] += contents
340
    else:
341
        f = open(full_path, "a")
342
        f.write(contents)
343
        f.close()
9402 Boppan 344
 
8957 Boppan 345
class AsmElement:
9407 Boppan 346
    def __init__(self, location, name, comment):
347
        global warnings
8980 Boppan 348
 
9407 Boppan 349
        # If the element was constructed during this execution then the element is new
350
        self.new = True
351
        self.location = location
352
        self.file = self.location.split(':')[0].replace('\\', '/')
353
        self.line = self.location.split(':')[1]
354
        self.name = name
355
        self.comment = comment
8957 Boppan 356
 
9407 Boppan 357
        if self.comment == '':
358
            warnings += f'{self.location}: Undocumented element\n'
8980 Boppan 359
 
9407 Boppan 360
    def dump(self):
361
        print(f"\n{self.location}: {self.name}")
362
        print(f"{self.comment}")
8957 Boppan 363
 
9407 Boppan 364
    def emit(self, dest, doxycomment = '', declaration = ''):
365
        # Do not emit anything if the symbol is marked as hidden in its comment
366
        if '@dont_give_a_doxygen' in self.comment:
367
            return
8977 Boppan 368
 
9407 Boppan 369
        global warnings
370
        # Redefine default declaration
371
        if declaration == '':
372
            declaration = f'#define {self.name}'
373
        # Check doxycomment
374
        if not doxycomment.endswith('\n'):
375
            doxycomment += '\n'
376
        if doxycomment.split('@brief ')[1][0].islower():
377
            warnings += f"{self.location}: Brief comment starting from lowercase\n"
378
        # Build contents to emit
379
        contents = ''
380
        contents += '/**\n'
381
        contents += doxycomment
382
        contents += (f"@par Source\n" +
383
                     f"{self.file}:{self.line}\n")
384
        contents += '*/\n'
385
        contents += declaration
386
        contents += '\n\n'
387
        # Get path to file to emit this
388
        full_path = dest + '/' + self.file
389
        # Remove the file on first access if it was created by previous generation
390
        if full_path not in created_files:
391
            if os.path.isfile(full_path):
392
                os.remove(full_path)
393
            created_files.append(full_path)
394
        # Create directories need for the file
395
        os.makedirs(os.path.dirname(full_path), exist_ok=True)
396
        contents = ''.join([i if ord(i) < 128 else '?' for i in contents])
8957 Boppan 397
 
9407 Boppan 398
        append_file(full_path, contents)
9402 Boppan 399
 
8957 Boppan 400
class AsmVariable(AsmElement):
9407 Boppan 401
    def __init__(self, location, name, comment, type, init):
402
        super().__init__(location, name, comment)
403
        self.type = type
404
        self.init = init
8855 Boppan 405
 
9407 Boppan 406
    def dump(self):
407
        super().dump()
408
        print(f"(Variable)\n---")
8855 Boppan 409
 
9407 Boppan 410
    def emit(self, dest):
411
        # Build doxycomment specific for the variable
412
        doxycomment = ''
413
        doxycomment += self.comment
414
        if '@brief' not in doxycomment:
415
            doxycomment = '@brief ' + doxycomment
416
        doxycomment += (f"@par Initial value\n" +
417
                        f"{self.init}\n")
418
        # Build the declaration
419
        name = self.name.replace(".", "_")
420
        var_type = self.type.replace(".", "_")
421
        declaration = f"{var_type} {name};"
422
        # Emit this
423
        super().emit(dest, doxycomment, declaration)
8855 Boppan 424
 
8957 Boppan 425
class AsmFunction(AsmElement):
9407 Boppan 426
    def __init__(self, location, name, comment, calling_convention, args, used_regs):
427
        super().__init__(location, name, comment)
428
        self.calling_convention = calling_convention
429
        self.args = args
430
        self.used_regs = used_regs
8855 Boppan 431
 
9407 Boppan 432
    def dump(self):
433
        super().dump()
434
        print(f"(Function)\n---")
8855 Boppan 435
 
9407 Boppan 436
    def emit(self, dest):
437
        # Build doxycomment specific for the variable
438
        doxycomment = ''
439
        doxycomment += self.comment
440
        if '@brief' not in doxycomment:
441
            doxycomment = '@brief ' + doxycomment
442
        # If there was no arguments, maybe that's just a label
443
        # then parse parameters from its comment
444
        if len(self.args) == 0 and '@param' in self.comment:
445
            i = 0
446
            while '@param' in self.comment[i:]:
447
                i = self.comment.index('@param', i)
448
                # Skip '@param'
449
                i += len('@param')
450
                # Skip spaces after '@param'
451
                while self.comment[i].isspace():
452
                    i += 1
453
                # Get the parameter name
454
                name = ''
455
                while is_id(self.comment[i]):
456
                    name += self.comment[i]
457
                    i += 1
458
                # Save the parameter
459
                self.args.append((name, 'arg_t'))
460
        # Build the arg list for declaration
461
        arg_list = '('
462
        if len(self.args) > 0:
463
            argc = 0
464
            for arg in self.args:
465
                if argc != 0:
466
                    arg_list += ", "
467
                arg_list += f"{arg[1]} {arg[0]}"
468
                argc += 1
469
        arg_list += ')'
470
        # Build the declaration
471
        name = self.name.replace(".", "_")
472
        declaration = f"void {name}{arg_list};"
473
        # Emit this
474
        super().emit(dest, doxycomment, declaration)
8855 Boppan 475
 
8957 Boppan 476
class AsmLabel(AsmElement):
9407 Boppan 477
    def __init__(self, location, name, comment):
478
        super().__init__(location, name, comment)
8855 Boppan 479
 
9407 Boppan 480
    def dump(self):
481
        super().dump()
482
        print(f"(Label)\n---")
8855 Boppan 483
 
9407 Boppan 484
    def emit(self, dest):
485
        # Build doxycomment specific for the variable
486
        doxycomment = ''
487
        doxycomment += self.comment
488
        if '@brief' not in doxycomment:
489
            doxycomment = '@brief ' + doxycomment
490
        # Build the declaration
491
        name = self.name.replace(".", "_")
492
        declaration = f"label {name};"
493
        # Emit this
494
        super().emit(dest, doxycomment, declaration)
8855 Boppan 495
 
8957 Boppan 496
class AsmMacro(AsmElement):
9407 Boppan 497
    def __init__(self, location, name, comment, args):
498
        super().__init__(location, name, comment)
499
        self.args = args
8855 Boppan 500
 
9407 Boppan 501
    def dump(self):
502
        super().dump()
503
        print(f"(Macro)\n---")
8855 Boppan 504
 
9407 Boppan 505
    def emit(self, dest):
506
        # Construct arg list without '['s, ']'s and '*'s
507
        args = [arg for arg in self.args if arg not in "[]*"]
508
        # Construct C-like arg list
509
        arg_list = ""
510
        if len(args) > 0:
511
            arg_list += '('
512
            argc = 0
513
            for arg in args:
514
                if argc != 0:
515
                    arg_list += ", "
516
                arg_list += arg
517
                argc += 1
518
            arg_list += ')'
519
        # Build doxycomment
520
        doxycomment = ''
521
        doxycomment += self.comment
522
        if '@brief' not in doxycomment:
523
            doxycomment = '@brief ' + doxycomment
524
        # Build declaration
525
        declaration = f"#define {self.name}{arg_list}"
526
        # Emit this
527
        super().emit(dest, doxycomment, declaration)
8855 Boppan 528
 
8957 Boppan 529
class AsmStruct(AsmElement):
9407 Boppan 530
    def __init__(self, location, name, comment, members):
531
        super().__init__(location, name, comment)
532
        self.members = members
8855 Boppan 533
 
9407 Boppan 534
    def dump(self):
535
        super().dump()
536
        print(f"(Struct)\n---")
8855 Boppan 537
 
9407 Boppan 538
    def emit(self, dest):
539
        # Build doxycomment
540
        doxycomment = ''
541
        doxycomment += self.comment
542
        if '@brief' not in doxycomment:
543
            doxycomment = '@brief ' + doxycomment
544
        doxycomment += '\n'
545
        # Build declaration
546
        declaration = f"struct {self.name}" + " {\n"
547
        for member in self.members:
548
            if type(member) == AsmVariable:
549
                declaration += f'\t{member.type} {member.name}; /**< {member.comment} */\n'
550
        declaration += '};'
551
        # Emit this
552
        super().emit(dest, doxycomment, declaration)
8855 Boppan 553
 
8957 Boppan 554
class AsmUnion(AsmElement):
9407 Boppan 555
    def __init__(self, location, name, comment, members):
556
        super().__init__(location, name, comment)
557
        self.members = members
8855 Boppan 558
 
9407 Boppan 559
    def dump(self):
560
        super().dump()
561
        print(f"(Union)\n---")
8855 Boppan 562
 
9407 Boppan 563
    def emit(self, dest):
564
        # Build doxycomment
565
        doxycomment = ''
566
        doxycomment += self.comment
567
        if '@brief' not in doxycomment:
568
            doxycomment = '@brief ' + doxycomment
569
        # Build declaration
570
        declaration = f"union {self.name}" + " {};"
571
        # Emit this
572
        super().emit(dest, doxycomment, declaration)
8855 Boppan 573
 
8957 Boppan 574
class VariableNameIsMacroName:
9407 Boppan 575
    def __init__(self, name):
576
        self.name = name
8855 Boppan 577
 
8957 Boppan 578
def is_id(c):
9407 Boppan 579
    return c.isprintable() and c not in "+-/*=<>()[]{};:,|&~#`'\" \n\r\t\v"
8855 Boppan 580
 
8957 Boppan 581
def is_starts_as_id(s):
9407 Boppan 582
    return not s[0].isdigit()
8957 Boppan 583
 
584
def parse_after_macro(r):
9407 Boppan 585
    location = r.location()
8957 Boppan 586
 
9407 Boppan 587
    # Skip spaces after the "macro" keyword
588
    r.skip_spaces()
589
    # Read macro name
590
    name = ""
591
    while is_id(r.curr()) or r.curr() == '#':
592
        name += r.step()
593
    # Skip spaces after macro name
594
    r.skip_spaces()
595
    # Find all arguments
596
    args = []
597
    arg = ''
598
    while r.curr() and r.curr() != ';' and r.curr() != '{':
599
        # Collect identifier
600
        if is_id(r.curr()):
601
            arg += r.step()
602
        # Save the collected identifier
603
        elif r.curr() == ',':
604
            args.append(arg)
605
            arg = ''
606
            r.step()
607
        # Just push the '['
608
        elif r.curr() == '[':
609
            args.append(r.step())
610
        # Just push the identifier and get ']' ready to be pushed on next comma
611
        elif r.curr() == ']':
612
            args.append(arg)
613
            arg = r.step()
614
        # Just push the identifier and get '*' ready to be pushed on next comma
615
        elif r.curr() == '*':
616
            args.append(arg)
617
            arg = r.step()
618
        # Just skip whitespaces
619
        elif r.curr().isspace():
620
            r.step()
621
        # Something unexpected
622
        else:
623
            raise Exception(f"Unexpected symbol '{r.curr()}' at index #{r.i} " +
624
                            f"in the macro declaration at {location} " +
625
                            f"(line: {r.lines[r.line_idx]})\n''")
626
    # Append the last argument
627
    if arg != '':
628
        args.append(arg)
629
    # Skip t spaces after the argument list
630
    r.skip_spaces()
631
    # Get a comment if it is: read till the end of the line and get the comment from the reader
632
    while r.curr() != '':
633
        r.step()
634
    comment = r.comment
635
    # Find end of the macro
636
    prev = ''
637
    while True:
638
        if r.curr() == '}' and prev != '\\':
639
            break
640
        elif r.curr() == '':
641
            prev = ''
642
            r.nextline()
643
            continue
644
        prev = r.step()
645
    # Build the output
646
    return AsmMacro(location, name, comment, args)
8855 Boppan 647
 
8957 Boppan 648
def parse_variable(r, first_word = None):
9407 Boppan 649
    global warnings
650
    location = r.location()
8825 Boppan 651
 
9407 Boppan 652
    # Skip spaces before variable name
653
    r.skip_spaces()
654
    # Get variable name
655
    name = ""
656
    # Read it if it was not supplied
657
    if first_word == None:
658
        while is_id(r.curr()):
659
            name += r.step()
660
    # Or use the supplied one instead
661
    else:
662
        name = first_word
663
    # Check the name
664
    # If it's 0 len, that means threr's something else than an identifier at the beginning
665
    if len(name) == 0:
666
        return None
667
    # If it starts from digit or othervice illegally it's illegal
668
    if not is_starts_as_id(name):
669
        return None
670
    # Get kind of the identifier from id2kind table
671
    kind = id_get_kind(name)
672
    # If it's a keyword, that's not a variable declaration
673
    if ID_KIND_KEYWORD in kind:
674
        return None
675
    # If it's a macro name, that's not a variable declaration
676
    if ID_KIND_MACRO_NAME in kind:
677
        return VariableNameIsMacroName(name)
678
    # If it's a datatype or a structure name that's not a variable declaration: that's just a data
679
    # don't document just a data for now
680
    if ID_KIND_STRUCT_NAME in kind or ID_KIND_FASM_TYPE in kind:
681
        return None
682
    # Skip spaces before type name
683
    r.skip_spaces()
684
    # Read type name
685
    var_type = ""
686
    while is_id(r.curr()):
687
        var_type += r.step()
688
    # Check the type name
689
    if len(var_type) == 0:
690
        # If there's no type identifier after the name
691
        # maybe the name is something meaningful for the next parser
692
        # return it
693
        return name
694
    # If it starts from digit or othervice illegally it's illegal
695
    if not is_starts_as_id(var_type):
696
        return None
697
    # Get kind of type identifier
698
    type_kind = id_get_kind(var_type)
699
    # If it's a keyword, that's not a variable declaration
700
    # return the two words of the lexical structure
701
    if ID_KIND_KEYWORD in type_kind:
702
        return (name, var_type)
703
    # Skip spaces before the value
704
    r.skip_spaces()
705
    # Read the value until the comment or end of the line
706
    value = ""
707
    while r.curr() != ';' and r.curr() != '' and r.curr() != '\n':
708
        value += r.step()
709
    # Skip spaces after the value
710
    r.skip_spaces()
711
    # Read till end of the line to get a comment from the reader
712
    while r.curr() != '':
713
        r.step()
714
    # Build the result
715
    return AsmVariable(location, name, r.comment, var_type, value)
8825 Boppan 716
 
8957 Boppan 717
def parse_after_struct(r, as_union = True):
9407 Boppan 718
    global warnings
719
    location = r.location()
8825 Boppan 720
 
9407 Boppan 721
    # Skip spaces after "struct" keyword
722
    r.skip_spaces()
723
    # Read struct name
724
    name = ""
725
    while is_id(r.curr()):
726
        name += r.step()
727
    # Read till end of the line and get the comment from the reader
728
    while r.curr() != '':
729
        r.step()
730
    comment = r.comment
731
    # Get to the next line to parse struct members
732
    r.nextline()
733
    # Parse struct members
734
    members = []
735
    while True:
736
        r.skip_spaces()
737
        var = parse_variable(r)
738
        if type(var) == AsmVariable:
739
            members.append(var)
740
        elif type(var) == str:
741
            if var == 'union':
742
                # Parse the union as a struct
743
                union = parse_after_struct(r, as_union = True)
744
                members.append(union)
745
                # Skip the ends of the union
746
                r.nextline()
747
            elif r.curr() == ':':
748
                warnings += f"{r.location()}: Skept the label in the struct\n"
749
            else:
750
                raise Exception(f"Garbage in struct member at {location} (got '{var}' identifier)")
751
        elif type(var) == VariableNameIsMacroName:
752
            if var.name == 'ends':
753
                break
754
        r.nextline()
755
    # Return the result
756
    if as_union:
757
        return AsmStruct(location, name, comment, members)
758
    else:
759
        return AsmUnion(location, name, comment, members)
8825 Boppan 760
 
8963 Boppan 761
def parse_after_proc(r):
9407 Boppan 762
    # Get proc name
763
    name = r.fetch_identifier()
764
    # Next identifier after the proc name
765
    identifier = r.fetch_identifier()
766
    # Check if the id is 'stdcall' or 'c' (calling convention specifier)
767
    # and if so - save the convention and lookup the next identifier
768
    calling_convention = ''
769
    if identifier == 'stdcall' or identifier == 'c':
770
        calling_convention = identifier
771
        # If next is a comma, just skip it
772
        if r.curr() == ',':
773
            r.step()
774
        # Read the next identifier
775
        identifier = r.fetch_identifier()
776
    # Check if the id is 'uses' (used register list specifier)
777
    # and if so save the used register list
778
    used_regs = []
779
    if identifier == 'uses':
780
        # Read the registers
781
        while True:
782
            reg_name = r.fetch_identifier()
783
            if reg_name != '':
784
                used_regs.append(reg_name)
785
            else:
786
                break
787
        # If next is a comma, just skip it
788
        if r.curr() == ',':
789
            r.step()
790
        # Read the next identifier
791
        identifier = r.fetch_identifier()
792
    # Check if there are argument identifiers
793
    args = []
794
    while identifier != '':
795
        arg_name = identifier
796
        arg_type = 'arg_t'
797
        # Skip spaces after argument name
798
        r.skip_spaces()
799
        # If there's a ':' after the name - the next identifier is type
800
        if r.curr() == ':':
801
            r.step()
802
            arg_type = r.fetch_identifier()
803
        # If there's a comma - there's one more argument
804
        # else no arguments anymore
805
        if r.curr() == ',':
806
            r.step()
807
            identifier = r.fetch_identifier()
808
        else:
809
            identifier = ''
810
        args.append((arg_name, arg_type))
811
    # Get to the end of the line and get a comment from the reader
812
    while r.curr() != '':
813
        r.step()
814
    comment = r.comment
815
    # Build the element
816
    return AsmFunction(r.location(), name, comment, calling_convention, args, used_regs)
8963 Boppan 817
 
8957 Boppan 818
def get_declarations(asm_file_contents, asm_file_name):
9407 Boppan 819
    r = AsmReader(asm_file_name)
8825 Boppan 820
 
9407 Boppan 821
    while not r.no_lines():
822
        # Skip leading spaces
823
        r.skip_spaces()
824
        # Skip the line if it's starting with a comment
825
        if r.curr() == ';':
826
            r.nextline()
827
            continue
828
        # Get first word
829
        first_word = ""
830
        while is_id(r.curr()):
831
            first_word += r.step()
832
        # Match macro declaration
833
        if first_word == "macro":
834
            macro = parse_after_macro(r)
835
            elements.append(macro)
836
            id_add_kind(macro.name, ID_KIND_MACRO_NAME)
837
        # Match structure declaration
838
        elif first_word == "struct":
839
            struct = parse_after_struct(r)
840
            elements.append(struct)
841
            id_add_kind(struct.name, ID_KIND_STRUCT_NAME)
842
        # Match function definition
843
        elif first_word == "proc":
844
            proc = parse_after_proc(r)
845
            elements.append(proc)
846
        elif first_word == 'format':
847
            # Skip the format directive
848
            pass
849
        elif first_word == 'include':
850
            # Skip the include directive
851
            pass
852
        elif first_word == 'if':
853
            # Skip the conditional directive
854
            pass
855
        elif first_word == 'repeat':
856
            # Skip the repeat directive
857
            pass
858
        elif first_word == 'purge':
859
            while True:
860
                # Skip spaces after the 'purge' keyword or after the comma what separated the previous macro name
861
                r.skip_spaces()
862
                # Get the purged macro name
863
                name = ''
864
                while is_id(r.curr()):
865
                    name += r.step()
866
                # Remove the purged macro from the macro names list
867
                try:
868
                    id_remove_kind(name, ID_KIND_MACRO_NAME)
869
                except:
870
                    pass
871
                # Skip spaces after the name
872
                r.skip_spaces()
873
                # If it's comma (',') after then that's not the last purged macro, continue purging
874
                if r.curr() == ',':
875
                    r.step()
876
                    continue
877
                # Here we purged all the macros should be purged
878
                break
879
        # Match label or a variable
880
        elif len(first_word) != 0:
881
            # Skip spaces after the identifier
882
            r.skip_spaces()
883
            # Match a variable
884
            var = parse_variable(r, first_word)
885
            if type(var) == AsmVariable:
886
                elements.append(var)
887
            # If it wasn't a variable but there was an identifier
888
            # Maybe that's a label and the identifier is the label name
889
            # The parse_variable returns the first found or supplied identifier
890
            # In this case it returns the first_word which is supplied
891
            # If it didn't match a type identifier after the word
892
            elif type(var) == str:
893
                name = var
894
                # Match label beginning (':' after name)
895
                if r.curr() == ':':
896
                    # Get to the end of the line and get the coment from the reader
897
                    while r.curr() != '':
898
                        r.step()
899
                    comment = r.comment
900
                    # Only handle non-local labels
901
                    if name[0] != '.' and name != "@@" and name != "$Revision":
902
                        if '@return' in comment or '@param' in comment:
903
                            element = AsmFunction(r.location(), name, comment, '', [], [])
904
                        else:
905
                            element = AsmLabel(r.location(), name, comment)
906
                        elements.append(element)
907
                elif r.curr() == '=':
908
                    # Save the identifier as a set constant
909
                    id_add_kind(first_word, ID_KIND_SET_CONSTANT)
910
            elif type(var) == tuple:
911
                (word_one, word_two) = var
912
                if word_two == 'equ':
913
                    # Save the identifier as an equated constant
914
                    id_add_kind(word_one, ID_KIND_EQUATED_CONSTANT)
915
        r.nextline()
8825 Boppan 916
 
8966 Boppan 917
def it_neds_to_be_parsed(source_file):
9407 Boppan 918
    # If there's no symbols file saved - parse it anyway
919
    # cause we need to create the symbols file and use it
920
    # if we gonna generate proper doxygen
921
    if not os.path.isfile('asmxygen.elements.pickle'):
922
        return True
923
    dest = doxygen_src_path + '/' + source_file
924
    # If there's no the doxygen file it should be compiled to
925
    # then yes, we should compile it to doxygen
926
    if not os.path.isfile(dest):
927
        return True
928
    source_change_time = os.path.getmtime(source_file)
929
    dest_change_file = os.path.getmtime(dest)
930
    # If the source is newer than the doxygen it was compiled to
931
    # then the source should be recompiled (existing doxygen is old)
932
    if source_change_time > dest_change_file:
933
        return True
934
    return False
8966 Boppan 935
 
8834 Boppan 936
def handle_file(handled_files, asm_file_name, subdir = "."):
9407 Boppan 937
    global elements
938
    # Canonicalize the file path and get it relative to cwd
939
    cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
940
    asm_file_name = os.path.realpath(asm_file_name)
941
    asm_file_name = asm_file_name[len(cwd) + 1:]
942
    # If it's lang.inc - skip it
943
    if asm_file_name == 'lang.inc':
944
        return
945
    # If the file was handled in this execution before - skip it
946
    if asm_file_name in handled_files:
947
        return
948
    # Say that the file was handled in this execution
949
    handled_files.append(asm_file_name)
950
    # Check if the file should be parsed (if it was modified or wasn't parsed yet)
951
    should_get_declarations = True
952
    if not it_neds_to_be_parsed(asm_file_name):
953
        print(f"Skipping {asm_file_name} (already newest)")
954
        should_get_declarations = False
955
    else:
956
        print(f"Handling {asm_file_name}")
957
        # Remove elements parsed from this file before if any
958
        elements_to_remove = [x for x in elements if x.location.split(':')[0] == asm_file_name]
959
        elements = [x for x in elements if x.location.split(':')[0] != asm_file_name]
960
        # Forget types of identifiers of names of the removed elements
961
        for element in elements_to_remove:
962
            if type(element) == AsmStruct:
963
                id_remove_kind(element.name, ID_KIND_STRUCT_NAME)
964
            elif type(element) == AsmMacro:
965
                id_remove_kind(element.name, ID_KIND_MACRO_NAME)
966
    # Read the source
967
    asm_file_contents = open(asm_file_name, "r", encoding="utf-8").read()
968
    # Find includes, fix their paths and handle em recoursively
969
    includes = re.findall(r'^include (["\'])(.*)\1', asm_file_contents, flags=re.MULTILINE)
970
    for include in includes:
971
        include = include[1].replace('\\', '/');
972
        full_path = subdir + '/' + include;
973
        # If the path isn't valid, maybe that's not relative path
974
        if not os.path.isfile(full_path):
975
            full_path = include
976
        new_subdir = full_path.rsplit('/', 1)[0]
977
        handle_file(handled_files, full_path, new_subdir)
978
    # Only collect declarations from the file if it wasn't parsed before
979
    if should_get_declarations and not clean_generated_stuff:
980
        get_declarations(asm_file_contents, asm_file_name)
8825 Boppan 981
 
9401 Boppan 982
if __name__ == "__main__":
9407 Boppan 983
    link_root = "http://websvn.kolibrios.org/filedetails.php?repname=Kolibri+OS&path=/kernel/trunk"
9400 Boppan 984
 
9407 Boppan 985
    # Dict where an identifier is assicoated with a string
986
    # The string contains characters specifying flags
987
    # Available flags:
988
    #  k - Keyword
989
    #  m - Macro name
990
    #  t - fasm data Type name (db, rq, etc.)
991
    #  s - Struct type name
992
    #  e - equated constant (name equ value)
993
    #  = - set constants (name = value)
994
    ID_KIND_KEYWORD = 'k'
995
    ID_KIND_MACRO_NAME = 'm'
996
    ID_KIND_FASM_TYPE = 't'
997
    ID_KIND_STRUCT_NAME = 's'
998
    ID_KIND_EQUATED_CONSTANT = 'e'
999
    ID_KIND_SET_CONSTANT = '='
1000
    id2kind = {}
9399 Boppan 1001
 
9407 Boppan 1002
    for keyword in keywords:
1003
        id_add_kind(keyword, ID_KIND_KEYWORD)
9399 Boppan 1004
 
9407 Boppan 1005
    for fasm_type in fasm_types:
1006
        id_add_kind(fasm_type, ID_KIND_FASM_TYPE)
9399 Boppan 1007
 
9407 Boppan 1008
    # Warning list
1009
    warnings = ""
9399 Boppan 1010
 
9407 Boppan 1011
    # Parameters
1012
    # Path to doxygen folder to make doxygen files in: -o 
1013
    doxygen_src_path = 'docs/doxygen'
1014
    # Remove generated doxygen files: --clean
1015
    clean_generated_stuff = False
1016
    # Dump all defined symbols: --dump
1017
    dump_symbols = False
1018
    # Print symbol stats: --stats
1019
    print_stats = False
1020
    # Do not write warnings file: --nowarn
1021
    enable_warnings = True
9400 Boppan 1022
 
9407 Boppan 1023
    # Parse arguments
1024
    parser = argparse.ArgumentParser()
1025
    parser.add_argument("-o", help="Doxygen output folder")
1026
    parser.add_argument("--clean", help="Remove generated files", action="store_true")
1027
    parser.add_argument("--dump", help="Dump all defined symbols", action="store_true")
1028
    parser.add_argument("--stats", help="Print symbol stats", action="store_true")
1029
    parser.add_argument("--nowarn", help="Do not write warnings file", action="store_true")
1030
    parser.add_argument("--noemit", help="Do not emit doxygen files (for testing)", action="store_true")
1031
    parser.add_argument("--debug", help="Show hashes of files (for testing)", action="store_true")
1032
    args = parser.parse_args()
1033
    doxygen_src_path = args.o if args.o else 'docs/doxygen'
1034
    clean_generated_stuff = args.clean
1035
    dump_symbols = args.dump
1036
    print_stats = args.stats
1037
    enable_warnings = not args.nowarn
1038
    noemit = args.noemit
1039
    debug_mode = args.debug
9399 Boppan 1040
 
9407 Boppan 1041
    # Variables, functions, labels, macros, structure types
1042
    elements = []
1043
    created_files = []
1044
    kernel_files = []
1045
    output_files = {} # If --debug then all the files are written here
8825 Boppan 1046
 
9407 Boppan 1047
    # Load remembered list of symbols
1048
    if os.path.isfile('asmxygen.elements.pickle'):
1049
        print('Reading existing dump of symbols')
1050
        (elements, id2kind) = pickle.load(open('asmxygen.elements.pickle', 'rb'))
8990 Boppan 1051
 
9407 Boppan 1052
    handle_file(kernel_files, "./kernel.asm");
8825 Boppan 1053
 
9407 Boppan 1054
    if dump_symbols:
1055
        stdout = sys.stdout
1056
        sys.stdout = open('asmxygen.dump.txt', 'w', encoding = 'utf-8')
1057
        for asm_element in elements:
1058
            asm_element.dump()
1059
        sys.stdout = stdout
8825 Boppan 1060
 
9407 Boppan 1061
    if clean_generated_stuff:
1062
        kernel_files_set = set(kernel_files)
1063
        for file in kernel_files:
1064
            doxygen_file = f"{doxygen_src_path}/{file}"
1065
            if (os.path.isfile(doxygen_file)):
1066
                print(f"Removing {file}... ", end = '')
1067
                os.remove(doxygen_file)
1068
                print("Done.")
1069
    elif not noemit:
1070
        print(f"Writing doumented sources to {doxygen_src_path}")
8834 Boppan 1071
 
9407 Boppan 1072
        i = 0
1073
        new_elements = [x for x in elements if x.new]
1074
        for element in new_elements:
1075
            print(f"[{i + 1}/{len(new_elements)}] Emitting {element.name} from {element.location}")
1076
            element.emit(doxygen_src_path)
1077
            i += 1
8855 Boppan 1078
 
9407 Boppan 1079
        print(f"Writing dump of symbols to asmxygen.elements.pickle")
8990 Boppan 1080
 
9407 Boppan 1081
        # Now when the new elements already was written, there's no new elements anymore
1082
        for element in elements:
1083
            element.new = False
1084
        pickle.dump((elements, id2kind), open('asmxygen.elements.pickle', 'wb'))
8990 Boppan 1085
 
9407 Boppan 1086
    if print_stats:
1087
        var_count = 0
1088
        mac_count = 0
1089
        lab_count = 0
1090
        fun_count = 0
1091
        uni_count = 0
1092
        str_count = 0
1093
        for element in elements:
1094
            if type(element) == AsmVariable:
1095
                var_count += 1
1096
            elif type(element) == AsmMacro:
1097
                mac_count += 1
1098
            elif type(element) == AsmLabel:
1099
                lab_count += 1
1100
            elif type(element) == AsmFunction:
1101
                fun_count += 1
1102
            elif type(element) == AsmUnion:
1103
                uni_count += 1
1104
            elif type(element) == AsmStruct:
1105
                str_count += 1
1106
        print(f'Parsed variable count: {var_count}')
1107
        print(f'Parsed macro count: {mac_count}')
1108
        print(f'Parsed label count: {lab_count}')
1109
        print(f'Parsed function count: {fun_count}')
1110
        print(f'Parsed union type count: {uni_count}')
1111
        print(f'Parsed structure type count: {str_count}')
8982 Boppan 1112
 
9407 Boppan 1113
    if enable_warnings:
1114
        open('asmxygen.txt', "w", encoding = "utf-8").write(warnings)
9402 Boppan 1115
 
9407 Boppan 1116
    if debug_mode:
1117
        hash_per_file = ""
1118
        for file in output_files:
1119
            h = hashlib.sha1(bytes(output_files[file], "ascii")).hexdigest()
1120
            hash_per_file += f"{file}: {h}\n"
1121
        if not os.path.exists("asmxygen_hash_per_file.txt"):
1122
            open("asmxygen_hash_per_file.txt", "w").write(hash_per_file)
1123
            print("NEW")
1124
        else:
1125
            reference_hash_per_file = open("asmxygen_hash_per_file.txt").read()
1126
            if reference_hash_per_file != hash_per_file:
1127
                print(''.join(difflib.ndiff(reference_hash_per_file, hash_per_file)))
1128
            else:
1129
                print("SUCCESS")