Imported Upstream version 2.9.4
[platform/upstream/libxml2.git] / doc / apibuild.py
1 #!/usr/bin/python -u
2 #
3 # This is the API builder, it parses the C sources and build the
4 # API formal description in XML.
5 #
6 # See Copyright for the status of this software.
7 #
8 # daniel@veillard.com
9 #
10 import os, sys
11 import string
12 import glob
13
14 debug=0
15 #debugsym='ignorableWhitespaceSAXFunc'
16 debugsym=None
17
18 #
19 # C parser analysis code
20 #
21 ignored_files = {
22   "trio": "too many non standard macros",
23   "trio.c": "too many non standard macros",
24   "trionan.c": "too many non standard macros",
25   "triostr.c": "too many non standard macros",
26   "acconfig.h": "generated portability layer",
27   "config.h": "generated portability layer",
28   "libxml.h": "internal only",
29   "testOOM.c": "out of memory tester",
30   "testOOMlib.h": "out of memory tester",
31   "testOOMlib.c": "out of memory tester",
32   "rngparser.c": "not yet integrated",
33   "rngparser.h": "not yet integrated",
34   "elfgcchack.h": "not a normal header",
35   "testHTML.c": "test tool",
36   "testReader.c": "test tool",
37   "testSchemas.c": "test tool",
38   "testXPath.c": "test tool",
39   "testAutomata.c": "test tool",
40   "testModule.c": "test tool",
41   "testRegexp.c": "test tool",
42   "testThreads.c": "test tool",
43   "testC14N.c": "test tool",
44   "testRelax.c": "test tool",
45   "testThreadsWin32.c": "test tool",
46   "testSAX.c": "test tool",
47   "testURI.c": "test tool",
48   "testapi.c": "generated regression tests",
49   "runtest.c": "regression tests program",
50   "runsuite.c": "regression tests program",
51   "tst.c": "not part of the library",
52   "test.c": "not part of the library",
53   "testdso.c": "test for dynamid shared libraries",
54   "testrecurse.c": "test for entities recursions",
55   "xzlib.h": "Internal API only 2.8.0",
56   "buf.h": "Internal API only 2.9.0",
57   "enc.h": "Internal API only 2.9.0",
58   "/save.h": "Internal API only 2.9.0",
59   "timsort.h": "Internal header only for xpath.c 2.9.0",
60 }
61
62 ignored_words = {
63   "WINAPI": (0, "Windows keyword"),
64   "LIBXML_DLL_IMPORT": (0, "Special macro to flag external keywords"),
65   "XMLPUBVAR": (0, "Special macro for extern vars for win32"),
66   "XSLTPUBVAR": (0, "Special macro for extern vars for win32"),
67   "EXSLTPUBVAR": (0, "Special macro for extern vars for win32"),
68   "XMLPUBFUN": (0, "Special macro for extern funcs for win32"),
69   "XSLTPUBFUN": (0, "Special macro for extern funcs for win32"),
70   "EXSLTPUBFUN": (0, "Special macro for extern funcs for win32"),
71   "XMLCALL": (0, "Special macro for win32 calls"),
72   "XSLTCALL": (0, "Special macro for win32 calls"),
73   "XMLCDECL": (0, "Special macro for win32 calls"),
74   "EXSLTCALL": (0, "Special macro for win32 calls"),
75   "__declspec": (3, "Windows keyword"),
76   "__stdcall": (0, "Windows keyword"),
77   "ATTRIBUTE_UNUSED": (0, "macro keyword"),
78   "LIBEXSLT_PUBLIC": (0, "macro keyword"),
79   "X_IN_Y": (5, "macro function builder"),
80   "ATTRIBUTE_ALLOC_SIZE": (3, "macro for gcc checking extension"),
81   "ATTRIBUTE_PRINTF": (5, "macro for gcc printf args checking extension"),
82   "LIBXML_ATTR_FORMAT": (5, "macro for gcc printf args checking extension"),
83   "LIBXML_ATTR_ALLOC_SIZE": (3, "macro for gcc checking extension"),
84   "__XML_EXTERNC": (0, "Special macro added for os400"),
85 }
86
87 def escape(raw):
88     raw = raw.replace('&', '&')
89     raw = raw.replace('<', '&lt;')
90     raw = raw.replace('>', '&gt;')
91     raw = raw.replace("'", '&apos;')
92     raw = raw.replace('"', '&quot;')
93     return raw
94
95 def uniq(items):
96     d = {}
97     for item in items:
98         d[item]=1
99     return list(d.keys())
100
101 class identifier:
102     def __init__(self, name, header=None, module=None, type=None, lineno = 0,
103                  info=None, extra=None, conditionals = None):
104         self.name = name
105         self.header = header
106         self.module = module
107         self.type = type
108         self.info = info
109         self.extra = extra
110         self.lineno = lineno
111         self.static = 0
112         if conditionals == None or len(conditionals) == 0:
113             self.conditionals = None
114         else:
115             self.conditionals = conditionals[:]
116         if self.name == debugsym:
117             print("=> define %s : %s" % (debugsym, (module, type, info,
118                                          extra, conditionals)))
119
120     def __repr__(self):
121         r = "%s %s:" % (self.type, self.name)
122         if self.static:
123             r = r + " static"
124         if self.module != None:
125             r = r + " from %s" % (self.module)
126         if self.info != None:
127             r = r + " " +  repr(self.info)
128         if self.extra != None:
129             r = r + " " + repr(self.extra)
130         if self.conditionals != None:
131             r = r + " " + repr(self.conditionals)
132         return r
133
134
135     def set_header(self, header):
136         self.header = header
137     def set_module(self, module):
138         self.module = module
139     def set_type(self, type):
140         self.type = type
141     def set_info(self, info):
142         self.info = info
143     def set_extra(self, extra):
144         self.extra = extra
145     def set_lineno(self, lineno):
146         self.lineno = lineno
147     def set_static(self, static):
148         self.static = static
149     def set_conditionals(self, conditionals):
150         if conditionals == None or len(conditionals) == 0:
151             self.conditionals = None
152         else:
153             self.conditionals = conditionals[:]
154
155     def get_name(self):
156         return self.name
157     def get_header(self):
158         return self.module
159     def get_module(self):
160         return self.module
161     def get_type(self):
162         return self.type
163     def get_info(self):
164         return self.info
165     def get_lineno(self):
166         return self.lineno
167     def get_extra(self):
168         return self.extra
169     def get_static(self):
170         return self.static
171     def get_conditionals(self):
172         return self.conditionals
173
174     def update(self, header, module, type = None, info = None, extra=None,
175                conditionals=None):
176         if self.name == debugsym:
177             print("=> update %s : %s" % (debugsym, (module, type, info,
178                                          extra, conditionals)))
179         if header != None and self.header == None:
180             self.set_header(module)
181         if module != None and (self.module == None or self.header == self.module):
182             self.set_module(module)
183         if type != None and self.type == None:
184             self.set_type(type)
185         if info != None:
186             self.set_info(info)
187         if extra != None:
188             self.set_extra(extra)
189         if conditionals != None:
190             self.set_conditionals(conditionals)
191
192 class index:
193     def __init__(self, name = "noname"):
194         self.name = name
195         self.identifiers = {}
196         self.functions = {}
197         self.variables = {}
198         self.includes = {}
199         self.structs = {}
200         self.enums = {}
201         self.typedefs = {}
202         self.macros = {}
203         self.references = {}
204         self.info = {}
205
206     def add_ref(self, name, header, module, static, type, lineno, info=None, extra=None, conditionals = None):
207         if name[0:2] == '__':
208             return None
209         d = None
210         try:
211            d = self.identifiers[name]
212            d.update(header, module, type, lineno, info, extra, conditionals)
213         except:
214            d = identifier(name, header, module, type, lineno, info, extra, conditionals)
215            self.identifiers[name] = d
216
217         if d != None and static == 1:
218             d.set_static(1)
219
220         if d != None and name != None and type != None:
221             self.references[name] = d
222
223         if name == debugsym:
224             print("New ref: %s" % (d))
225
226         return d
227
228     def add(self, name, header, module, static, type, lineno, info=None, extra=None, conditionals = None):
229         if name[0:2] == '__':
230             return None
231         d = None
232         try:
233            d = self.identifiers[name]
234            d.update(header, module, type, lineno, info, extra, conditionals)
235         except:
236            d = identifier(name, header, module, type, lineno, info, extra, conditionals)
237            self.identifiers[name] = d
238
239         if d != None and static == 1:
240             d.set_static(1)
241
242         if d != None and name != None and type != None:
243             if type == "function":
244                 self.functions[name] = d
245             elif type == "functype":
246                 self.functions[name] = d
247             elif type == "variable":
248                 self.variables[name] = d
249             elif type == "include":
250                 self.includes[name] = d
251             elif type == "struct":
252                 self.structs[name] = d
253             elif type == "enum":
254                 self.enums[name] = d
255             elif type == "typedef":
256                 self.typedefs[name] = d
257             elif type == "macro":
258                 self.macros[name] = d
259             else:
260                 print("Unable to register type ", type)
261
262         if name == debugsym:
263             print("New symbol: %s" % (d))
264
265         return d
266
267     def merge(self, idx):
268         for id in list(idx.functions.keys()):
269               #
270               # macro might be used to override functions or variables
271               # definitions
272               #
273              if id in self.macros:
274                  del self.macros[id]
275              if id in self.functions:
276                  print("function %s from %s redeclared in %s" % (
277                     id, self.functions[id].header, idx.functions[id].header))
278              else:
279                  self.functions[id] = idx.functions[id]
280                  self.identifiers[id] = idx.functions[id]
281         for id in list(idx.variables.keys()):
282               #
283               # macro might be used to override functions or variables
284               # definitions
285               #
286              if id in self.macros:
287                  del self.macros[id]
288              if id in self.variables:
289                  print("variable %s from %s redeclared in %s" % (
290                     id, self.variables[id].header, idx.variables[id].header))
291              else:
292                  self.variables[id] = idx.variables[id]
293                  self.identifiers[id] = idx.variables[id]
294         for id in list(idx.structs.keys()):
295              if id in self.structs:
296                  print("struct %s from %s redeclared in %s" % (
297                     id, self.structs[id].header, idx.structs[id].header))
298              else:
299                  self.structs[id] = idx.structs[id]
300                  self.identifiers[id] = idx.structs[id]
301         for id in list(idx.typedefs.keys()):
302              if id in self.typedefs:
303                  print("typedef %s from %s redeclared in %s" % (
304                     id, self.typedefs[id].header, idx.typedefs[id].header))
305              else:
306                  self.typedefs[id] = idx.typedefs[id]
307                  self.identifiers[id] = idx.typedefs[id]
308         for id in list(idx.macros.keys()):
309               #
310               # macro might be used to override functions or variables
311               # definitions
312               #
313              if id in self.variables:
314                  continue
315              if id in self.functions:
316                  continue
317              if id in self.enums:
318                  continue
319              if id in self.macros:
320                  print("macro %s from %s redeclared in %s" % (
321                     id, self.macros[id].header, idx.macros[id].header))
322              else:
323                  self.macros[id] = idx.macros[id]
324                  self.identifiers[id] = idx.macros[id]
325         for id in list(idx.enums.keys()):
326              if id in self.enums:
327                  print("enum %s from %s redeclared in %s" % (
328                     id, self.enums[id].header, idx.enums[id].header))
329              else:
330                  self.enums[id] = idx.enums[id]
331                  self.identifiers[id] = idx.enums[id]
332
333     def merge_public(self, idx):
334         for id in list(idx.functions.keys()):
335              if id in self.functions:
336                  # check that function condition agrees with header
337                  if idx.functions[id].conditionals != \
338                     self.functions[id].conditionals:
339                      print("Header condition differs from Function for %s:" \
340                         % id)
341                      print("  H: %s" % self.functions[id].conditionals)
342                      print("  C: %s" % idx.functions[id].conditionals)
343                  up = idx.functions[id]
344                  self.functions[id].update(None, up.module, up.type, up.info, up.extra)
345          #     else:
346          #         print "Function %s from %s is not declared in headers" % (
347          #                id, idx.functions[id].module)
348          # TODO: do the same for variables.
349
350     def analyze_dict(self, type, dict):
351         count = 0
352         public = 0
353         for name in list(dict.keys()):
354             id = dict[name]
355             count = count + 1
356             if id.static == 0:
357                 public = public + 1
358         if count != public:
359             print("  %d %s , %d public" % (count, type, public))
360         elif count != 0:
361             print("  %d public %s" % (count, type))
362
363
364     def analyze(self):
365         self.analyze_dict("functions", self.functions)
366         self.analyze_dict("variables", self.variables)
367         self.analyze_dict("structs", self.structs)
368         self.analyze_dict("typedefs", self.typedefs)
369         self.analyze_dict("macros", self.macros)
370
371 class CLexer:
372     """A lexer for the C language, tokenize the input by reading and
373        analyzing it line by line"""
374     def __init__(self, input):
375         self.input = input
376         self.tokens = []
377         self.line = ""
378         self.lineno = 0
379
380     def getline(self):
381         line = ''
382         while line == '':
383             line = self.input.readline()
384             if not line:
385                 return None
386             self.lineno = self.lineno + 1
387             line = line.lstrip()
388             line = line.rstrip()
389             if line == '':
390                 continue
391             while line[-1] == '\\':
392                 line = line[:-1]
393                 n = self.input.readline()
394                 self.lineno = self.lineno + 1
395                 n = n.lstrip()
396                 n = n.rstrip()
397                 if not n:
398                     break
399                 else:
400                     line = line + n
401         return line
402
403     def getlineno(self):
404         return self.lineno
405
406     def push(self, token):
407         self.tokens.insert(0, token);
408
409     def debug(self):
410         print("Last token: ", self.last)
411         print("Token queue: ", self.tokens)
412         print("Line %d end: " % (self.lineno), self.line)
413
414     def token(self):
415         while self.tokens == []:
416             if self.line == "":
417                 line = self.getline()
418             else:
419                 line = self.line
420                 self.line = ""
421             if line == None:
422                 return None
423
424             if line[0] == '#':
425                 self.tokens = list(map((lambda x: ('preproc', x)),
426                                   line.split()))
427                 break;
428             l = len(line)
429             if line[0] == '"' or line[0] == "'":
430                 end = line[0]
431                 line = line[1:]
432                 found = 0
433                 tok = ""
434                 while found == 0:
435                     i = 0
436                     l = len(line)
437                     while i < l:
438                         if line[i] == end:
439                             self.line = line[i+1:]
440                             line = line[:i]
441                             l = i
442                             found = 1
443                             break
444                         if line[i] == '\\':
445                             i = i + 1
446                         i = i + 1
447                     tok = tok + line
448                     if found == 0:
449                         line = self.getline()
450                         if line == None:
451                             return None
452                 self.last = ('string', tok)
453                 return self.last
454
455             if l >= 2 and line[0] == '/' and line[1] == '*':
456                 line = line[2:]
457                 found = 0
458                 tok = ""
459                 while found == 0:
460                     i = 0
461                     l = len(line)
462                     while i < l:
463                         if line[i] == '*' and i+1 < l and line[i+1] == '/':
464                             self.line = line[i+2:]
465                             line = line[:i-1]
466                             l = i
467                             found = 1
468                             break
469                         i = i + 1
470                     if tok != "":
471                         tok = tok + "\n"
472                     tok = tok + line
473                     if found == 0:
474                         line = self.getline()
475                         if line == None:
476                             return None
477                 self.last = ('comment', tok)
478                 return self.last
479             if l >= 2 and line[0] == '/' and line[1] == '/':
480                 line = line[2:]
481                 self.last = ('comment', line)
482                 return self.last
483             i = 0
484             while i < l:
485                 if line[i] == '/' and i+1 < l and line[i+1] == '/':
486                     self.line = line[i:]
487                     line = line[:i]
488                     break
489                 if line[i] == '/' and i+1 < l and line[i+1] == '*':
490                     self.line = line[i:]
491                     line = line[:i]
492                     break
493                 if line[i] == '"' or line[i] == "'":
494                     self.line = line[i:]
495                     line = line[:i]
496                     break
497                 i = i + 1
498             l = len(line)
499             i = 0
500             while i < l:
501                 if line[i] == ' ' or line[i] == '\t':
502                     i = i + 1
503                     continue
504                 o = ord(line[i])
505                 if (o >= 97 and o <= 122) or (o >= 65 and o <= 90) or \
506                    (o >= 48 and o <= 57):
507                     s = i
508                     while i < l:
509                         o = ord(line[i])
510                         if (o >= 97 and o <= 122) or (o >= 65 and o <= 90) or \
511                            (o >= 48 and o <= 57) or \
512                            (" \t(){}:;,+-*/%&!|[]=><".find(line[i])) == -1:
513                             i = i + 1
514                         else:
515                             break
516                     self.tokens.append(('name', line[s:i]))
517                     continue
518                 if "(){}:;,[]".find(line[i]) != -1:
519 #                 if line[i] == '(' or line[i] == ')' or line[i] == '{' or \
520 #                    line[i] == '}' or line[i] == ':' or line[i] == ';' or \
521 #                    line[i] == ',' or line[i] == '[' or line[i] == ']':
522                     self.tokens.append(('sep', line[i]))
523                     i = i + 1
524                     continue
525                 if "+-*><=/%&!|.".find(line[i]) != -1:
526 #                 if line[i] == '+' or line[i] == '-' or line[i] == '*' or \
527 #                    line[i] == '>' or line[i] == '<' or line[i] == '=' or \
528 #                    line[i] == '/' or line[i] == '%' or line[i] == '&' or \
529 #                    line[i] == '!' or line[i] == '|' or line[i] == '.':
530                     if line[i] == '.' and  i + 2 < l and \
531                        line[i+1] == '.' and line[i+2] == '.':
532                         self.tokens.append(('name', '...'))
533                         i = i + 3
534                         continue
535
536                     j = i + 1
537                     if j < l and (
538                        "+-*><=/%&!|".find(line[j]) != -1):
539 #                        line[j] == '+' or line[j] == '-' or line[j] == '*' or \
540 #                        line[j] == '>' or line[j] == '<' or line[j] == '=' or \
541 #                        line[j] == '/' or line[j] == '%' or line[j] == '&' or \
542 #                        line[j] == '!' or line[j] == '|'):
543                         self.tokens.append(('op', line[i:j+1]))
544                         i = j + 1
545                     else:
546                         self.tokens.append(('op', line[i]))
547                         i = i + 1
548                     continue
549                 s = i
550                 while i < l:
551                     o = ord(line[i])
552                     if (o >= 97 and o <= 122) or (o >= 65 and o <= 90) or \
553                        (o >= 48 and o <= 57) or (
554                         " \t(){}:;,+-*/%&!|[]=><".find(line[i]) == -1):
555 #                         line[i] != ' ' and line[i] != '\t' and
556 #                         line[i] != '(' and line[i] != ')' and
557 #                         line[i] != '{'  and line[i] != '}' and
558 #                         line[i] != ':' and line[i] != ';' and
559 #                         line[i] != ',' and line[i] != '+' and
560 #                         line[i] != '-' and line[i] != '*' and
561 #                         line[i] != '/' and line[i] != '%' and
562 #                         line[i] != '&' and line[i] != '!' and
563 #                         line[i] != '|' and line[i] != '[' and
564 #                         line[i] != ']' and line[i] != '=' and
565 #                         line[i] != '*' and line[i] != '>' and
566 #                         line[i] != '<'):
567                         i = i + 1
568                     else:
569                         break
570                 self.tokens.append(('name', line[s:i]))
571
572         tok = self.tokens[0]
573         self.tokens = self.tokens[1:]
574         self.last = tok
575         return tok
576
577 class CParser:
578     """The C module parser"""
579     def __init__(self, filename, idx = None):
580         self.filename = filename
581         if len(filename) > 2 and filename[-2:] == '.h':
582             self.is_header = 1
583         else:
584             self.is_header = 0
585         self.input = open(filename)
586         self.lexer = CLexer(self.input)
587         if idx == None:
588             self.index = index()
589         else:
590             self.index = idx
591         self.top_comment = ""
592         self.last_comment = ""
593         self.comment = None
594         self.collect_ref = 0
595         self.no_error = 0
596         self.conditionals = []
597         self.defines = []
598
599     def collect_references(self):
600         self.collect_ref = 1
601
602     def stop_error(self):
603         self.no_error = 1
604
605     def start_error(self):
606         self.no_error = 0
607
608     def lineno(self):
609         return self.lexer.getlineno()
610
611     def index_add(self, name, module, static, type, info=None, extra = None):
612         if self.is_header == 1:
613             self.index.add(name, module, module, static, type, self.lineno(),
614                            info, extra, self.conditionals)
615         else:
616             self.index.add(name, None, module, static, type, self.lineno(),
617                            info, extra, self.conditionals)
618
619     def index_add_ref(self, name, module, static, type, info=None,
620                       extra = None):
621         if self.is_header == 1:
622             self.index.add_ref(name, module, module, static, type,
623                                self.lineno(), info, extra, self.conditionals)
624         else:
625             self.index.add_ref(name, None, module, static, type, self.lineno(),
626                                info, extra, self.conditionals)
627
628     def warning(self, msg):
629         if self.no_error:
630             return
631         print(msg)
632
633     def error(self, msg, token=-1):
634         if self.no_error:
635             return
636
637         print("Parse Error: " + msg)
638         if token != -1:
639             print("Got token ", token)
640         self.lexer.debug()
641         sys.exit(1)
642
643     def debug(self, msg, token=-1):
644         print("Debug: " + msg)
645         if token != -1:
646             print("Got token ", token)
647         self.lexer.debug()
648
649     def parseTopComment(self, comment):
650         res = {}
651         lines = comment.split("\n")
652         item = None
653         for line in lines:
654             while line != "" and (line[0] == ' ' or line[0] == '\t'):
655                 line = line[1:]
656             while line != "" and line[0] == '*':
657                 line = line[1:]
658             while line != "" and (line[0] == ' ' or line[0] == '\t'):
659                 line = line[1:]
660             try:
661                 (it, line) = line.split(":", 1)
662                 item = it
663                 while line != "" and (line[0] == ' ' or line[0] == '\t'):
664                     line = line[1:]
665                 if item in res:
666                     res[item] = res[item] + " " + line
667                 else:
668                     res[item] = line
669             except:
670                 if item != None:
671                     if item in res:
672                         res[item] = res[item] + " " + line
673                     else:
674                         res[item] = line
675         self.index.info = res
676
677     def parseComment(self, token):
678         if self.top_comment == "":
679             self.top_comment = token[1]
680         if self.comment == None or token[1][0] == '*':
681             self.comment = token[1];
682         else:
683             self.comment = self.comment + token[1]
684         token = self.lexer.token()
685
686         if self.comment.find("DOC_DISABLE") != -1:
687             self.stop_error()
688
689         if self.comment.find("DOC_ENABLE") != -1:
690             self.start_error()
691
692         return token
693
694     #
695     # Parse a comment block associate to a typedef
696     #
697     def parseTypeComment(self, name, quiet = 0):
698         if name[0:2] == '__':
699             quiet = 1
700
701         args = []
702         desc = ""
703
704         if self.comment == None:
705             if not quiet:
706                 self.warning("Missing comment for type %s" % (name))
707             return((args, desc))
708         if self.comment[0] != '*':
709             if not quiet:
710                 self.warning("Missing * in type comment for %s" % (name))
711             return((args, desc))
712         lines = self.comment.split('\n')
713         if lines[0] == '*':
714             del lines[0]
715         if lines[0] != "* %s:" % (name):
716             if not quiet:
717                 self.warning("Misformatted type comment for %s" % (name))
718                 self.warning("  Expecting '* %s:' got '%s'" % (name, lines[0]))
719             return((args, desc))
720         del lines[0]
721         while len(lines) > 0 and lines[0] == '*':
722             del lines[0]
723         desc = ""
724         while len(lines) > 0:
725             l = lines[0]
726             while len(l) > 0 and l[0] == '*':
727                 l = l[1:]
728             l = l.strip()
729             desc = desc + " " + l
730             del lines[0]
731
732         desc = desc.strip()
733
734         if quiet == 0:
735             if desc == "":
736                 self.warning("Type comment for %s lack description of the macro" % (name))
737
738         return(desc)
739     #
740     # Parse a comment block associate to a macro
741     #
742     def parseMacroComment(self, name, quiet = 0):
743         if name[0:2] == '__':
744             quiet = 1
745
746         args = []
747         desc = ""
748
749         if self.comment == None:
750             if not quiet:
751                 self.warning("Missing comment for macro %s" % (name))
752             return((args, desc))
753         if self.comment[0] != '*':
754             if not quiet:
755                 self.warning("Missing * in macro comment for %s" % (name))
756             return((args, desc))
757         lines = self.comment.split('\n')
758         if lines[0] == '*':
759             del lines[0]
760         if lines[0] != "* %s:" % (name):
761             if not quiet:
762                 self.warning("Misformatted macro comment for %s" % (name))
763                 self.warning("  Expecting '* %s:' got '%s'" % (name, lines[0]))
764             return((args, desc))
765         del lines[0]
766         while lines[0] == '*':
767             del lines[0]
768         while len(lines) > 0 and lines[0][0:3] == '* @':
769             l = lines[0][3:]
770             try:
771                 (arg, desc) = l.split(':', 1)
772                 desc=desc.strip()
773                 arg=arg.strip()
774             except:
775                 if not quiet:
776                     self.warning("Misformatted macro comment for %s" % (name))
777                     self.warning("  problem with '%s'" % (lines[0]))
778                 del lines[0]
779                 continue
780             del lines[0]
781             l = lines[0].strip()
782             while len(l) > 2 and l[0:3] != '* @':
783                 while l[0] == '*':
784                     l = l[1:]
785                 desc = desc + ' ' + l.strip()
786                 del lines[0]
787                 if len(lines) == 0:
788                     break
789                 l = lines[0]
790             args.append((arg, desc))
791         while len(lines) > 0 and lines[0] == '*':
792             del lines[0]
793         desc = ""
794         while len(lines) > 0:
795             l = lines[0]
796             while len(l) > 0 and l[0] == '*':
797                 l = l[1:]
798             l = l.strip()
799             desc = desc + " " + l
800             del lines[0]
801
802         desc = desc.strip()
803
804         if quiet == 0:
805             if desc == "":
806                 self.warning("Macro comment for %s lack description of the macro" % (name))
807
808         return((args, desc))
809
810      #
811      # Parse a comment block and merge the informations found in the
812      # parameters descriptions, finally returns a block as complete
813      # as possible
814      #
815     def mergeFunctionComment(self, name, description, quiet = 0):
816         if name == 'main':
817             quiet = 1
818         if name[0:2] == '__':
819             quiet = 1
820
821         (ret, args) = description
822         desc = ""
823         retdesc = ""
824
825         if self.comment == None:
826             if not quiet:
827                 self.warning("Missing comment for function %s" % (name))
828             return(((ret[0], retdesc), args, desc))
829         if self.comment[0] != '*':
830             if not quiet:
831                 self.warning("Missing * in function comment for %s" % (name))
832             return(((ret[0], retdesc), args, desc))
833         lines = self.comment.split('\n')
834         if lines[0] == '*':
835             del lines[0]
836         if lines[0] != "* %s:" % (name):
837             if not quiet:
838                 self.warning("Misformatted function comment for %s" % (name))
839                 self.warning("  Expecting '* %s:' got '%s'" % (name, lines[0]))
840             return(((ret[0], retdesc), args, desc))
841         del lines[0]
842         while lines[0] == '*':
843             del lines[0]
844         nbargs = len(args)
845         while len(lines) > 0 and lines[0][0:3] == '* @':
846             l = lines[0][3:]
847             try:
848                 (arg, desc) = l.split(':', 1)
849                 desc=desc.strip()
850                 arg=arg.strip()
851             except:
852                 if not quiet:
853                     self.warning("Misformatted function comment for %s" % (name))
854                     self.warning("  problem with '%s'" % (lines[0]))
855                 del lines[0]
856                 continue
857             del lines[0]
858             l = lines[0].strip()
859             while len(l) > 2 and l[0:3] != '* @':
860                 while l[0] == '*':
861                     l = l[1:]
862                 desc = desc + ' ' + l.strip()
863                 del lines[0]
864                 if len(lines) == 0:
865                     break
866                 l = lines[0]
867             i = 0
868             while i < nbargs:
869                 if args[i][1] == arg:
870                     args[i] = (args[i][0], arg, desc)
871                     break;
872                 i = i + 1
873             if i >= nbargs:
874                 if not quiet:
875                     self.warning("Unable to find arg %s from function comment for %s" % (
876                        arg, name))
877         while len(lines) > 0 and lines[0] == '*':
878             del lines[0]
879         desc = ""
880         while len(lines) > 0:
881             l = lines[0]
882             while len(l) > 0 and l[0] == '*':
883                 l = l[1:]
884             l = l.strip()
885             if len(l) >= 6 and  l[0:6] == "return" or l[0:6] == "Return":
886                 try:
887                     l = l.split(' ', 1)[1]
888                 except:
889                     l = ""
890                 retdesc = l.strip()
891                 del lines[0]
892                 while len(lines) > 0:
893                     l = lines[0]
894                     while len(l) > 0 and l[0] == '*':
895                         l = l[1:]
896                     l = l.strip()
897                     retdesc = retdesc + " " + l
898                     del lines[0]
899             else:
900                 desc = desc + " " + l
901                 del lines[0]
902
903         retdesc = retdesc.strip()
904         desc = desc.strip()
905
906         if quiet == 0:
907              #
908              # report missing comments
909              #
910             i = 0
911             while i < nbargs:
912                 if args[i][2] == None and args[i][0] != "void" and \
913                    ((args[i][1] != None) or (args[i][1] == '')):
914                     self.warning("Function comment for %s lacks description of arg %s" % (name, args[i][1]))
915                 i = i + 1
916             if retdesc == "" and ret[0] != "void":
917                 self.warning("Function comment for %s lacks description of return value" % (name))
918             if desc == "":
919                 self.warning("Function comment for %s lacks description of the function" % (name))
920
921         return(((ret[0], retdesc), args, desc))
922
923     def parsePreproc(self, token):
924         if debug:
925             print("=> preproc ", token, self.lexer.tokens)
926         name = token[1]
927         if name == "#include":
928             token = self.lexer.token()
929             if token == None:
930                 return None
931             if token[0] == 'preproc':
932                 self.index_add(token[1], self.filename, not self.is_header,
933                                 "include")
934                 return self.lexer.token()
935             return token
936         if name == "#define":
937             token = self.lexer.token()
938             if token == None:
939                 return None
940             if token[0] == 'preproc':
941                  # TODO macros with arguments
942                 name = token[1]
943                 lst = []
944                 token = self.lexer.token()
945                 while token != None and token[0] == 'preproc' and \
946                       token[1][0] != '#':
947                     lst.append(token[1])
948                     token = self.lexer.token()
949                 try:
950                     name = name.split('(') [0]
951                 except:
952                     pass
953                 info = self.parseMacroComment(name, not self.is_header)
954                 self.index_add(name, self.filename, not self.is_header,
955                                 "macro", info)
956                 return token
957
958         #
959         # Processing of conditionals modified by Bill 1/1/05
960         #
961         # We process conditionals (i.e. tokens from #ifdef, #ifndef,
962         # #if, #else and #endif) for headers and mainline code,
963         # store the ones from the header in libxml2-api.xml, and later
964         # (in the routine merge_public) verify that the two (header and
965         # mainline code) agree.
966         #
967         # There is a small problem with processing the headers. Some of
968         # the variables are not concerned with enabling / disabling of
969         # library functions (e.g. '__XML_PARSER_H__'), and we don't want
970         # them to be included in libxml2-api.xml, or involved in
971         # the check between the header and the mainline code.  To
972         # accomplish this, we ignore any conditional which doesn't include
973         # the string 'ENABLED'
974         #
975         if name == "#ifdef":
976             apstr = self.lexer.tokens[0][1]
977             try:
978                 self.defines.append(apstr)
979                 if apstr.find('ENABLED') != -1:
980                     self.conditionals.append("defined(%s)" % apstr)
981             except:
982                 pass
983         elif name == "#ifndef":
984             apstr = self.lexer.tokens[0][1]
985             try:
986                 self.defines.append(apstr)
987                 if apstr.find('ENABLED') != -1:
988                     self.conditionals.append("!defined(%s)" % apstr)
989             except:
990                 pass
991         elif name == "#if":
992             apstr = ""
993             for tok in self.lexer.tokens:
994                 if apstr != "":
995                     apstr = apstr + " "
996                 apstr = apstr + tok[1]
997             try:
998                 self.defines.append(apstr)
999                 if apstr.find('ENABLED') != -1:
1000                     self.conditionals.append(apstr)
1001             except:
1002                 pass
1003         elif name == "#else":
1004             if self.conditionals != [] and \
1005                self.defines[-1].find('ENABLED') != -1:
1006                 self.conditionals[-1] = "!(%s)" % self.conditionals[-1]
1007         elif name == "#endif":
1008             if self.conditionals != [] and \
1009                self.defines[-1].find('ENABLED') != -1:
1010                 self.conditionals = self.conditionals[:-1]
1011             self.defines = self.defines[:-1]
1012         token = self.lexer.token()
1013         while token != None and token[0] == 'preproc' and \
1014             token[1][0] != '#':
1015             token = self.lexer.token()
1016         return token
1017
1018      #
1019      # token acquisition on top of the lexer, it handle internally
1020      # preprocessor and comments since they are logically not part of
1021      # the program structure.
1022      #
1023     def token(self):
1024         global ignored_words
1025
1026         token = self.lexer.token()
1027         while token != None:
1028             if token[0] == 'comment':
1029                 token = self.parseComment(token)
1030                 continue
1031             elif token[0] == 'preproc':
1032                 token = self.parsePreproc(token)
1033                 continue
1034             elif token[0] == "name" and token[1] == "__const":
1035                 token = ("name", "const")
1036                 return token
1037             elif token[0] == "name" and token[1] == "__attribute":
1038                 token = self.lexer.token()
1039                 while token != None and token[1] != ";":
1040                     token = self.lexer.token()
1041                 return token
1042             elif token[0] == "name" and token[1] in ignored_words:
1043                 (n, info) = ignored_words[token[1]]
1044                 i = 0
1045                 while i < n:
1046                     token = self.lexer.token()
1047                     i = i + 1
1048                 token = self.lexer.token()
1049                 continue
1050             else:
1051                 if debug:
1052                     print("=> ", token)
1053                 return token
1054         return None
1055
1056      #
1057      # Parse a typedef, it records the type and its name.
1058      #
1059     def parseTypedef(self, token):
1060         if token == None:
1061             return None
1062         token = self.parseType(token)
1063         if token == None:
1064             self.error("parsing typedef")
1065             return None
1066         base_type = self.type
1067         type = base_type
1068          #self.debug("end typedef type", token)
1069         while token != None:
1070             if token[0] == "name":
1071                 name = token[1]
1072                 signature = self.signature
1073                 if signature != None:
1074                     type = type.split('(')[0]
1075                     d = self.mergeFunctionComment(name,
1076                             ((type, None), signature), 1)
1077                     self.index_add(name, self.filename, not self.is_header,
1078                                     "functype", d)
1079                 else:
1080                     if base_type == "struct":
1081                         self.index_add(name, self.filename, not self.is_header,
1082                                         "struct", type)
1083                         base_type = "struct " + name
1084                     else:
1085                         # TODO report missing or misformatted comments
1086                         info = self.parseTypeComment(name, 1)
1087                         self.index_add(name, self.filename, not self.is_header,
1088                                     "typedef", type, info)
1089                 token = self.token()
1090             else:
1091                 self.error("parsing typedef: expecting a name")
1092                 return token
1093              #self.debug("end typedef", token)
1094             if token != None and token[0] == 'sep' and token[1] == ',':
1095                 type = base_type
1096                 token = self.token()
1097                 while token != None and token[0] == "op":
1098                     type = type + token[1]
1099                     token = self.token()
1100             elif token != None and token[0] == 'sep' and token[1] == ';':
1101                 break;
1102             elif token != None and token[0] == 'name':
1103                 type = base_type
1104                 continue;
1105             else:
1106                 self.error("parsing typedef: expecting ';'", token)
1107                 return token
1108         token = self.token()
1109         return token
1110
1111      #
1112      # Parse a C code block, used for functions it parse till
1113      # the balancing } included
1114      #
1115     def parseBlock(self, token):
1116         while token != None:
1117             if token[0] == "sep" and token[1] == "{":
1118                 token = self.token()
1119                 token = self.parseBlock(token)
1120             elif token[0] == "sep" and token[1] == "}":
1121                 self.comment = None
1122                 token = self.token()
1123                 return token
1124             else:
1125                 if self.collect_ref == 1:
1126                     oldtok = token
1127                     token = self.token()
1128                     if oldtok[0] == "name" and oldtok[1][0:3] == "xml":
1129                         if token[0] == "sep" and token[1] == "(":
1130                             self.index_add_ref(oldtok[1], self.filename,
1131                                                 0, "function")
1132                             token = self.token()
1133                         elif token[0] == "name":
1134                             token = self.token()
1135                             if token[0] == "sep" and (token[1] == ";" or
1136                                token[1] == "," or token[1] == "="):
1137                                 self.index_add_ref(oldtok[1], self.filename,
1138                                                     0, "type")
1139                     elif oldtok[0] == "name" and oldtok[1][0:4] == "XML_":
1140                         self.index_add_ref(oldtok[1], self.filename,
1141                                             0, "typedef")
1142                     elif oldtok[0] == "name" and oldtok[1][0:7] == "LIBXML_":
1143                         self.index_add_ref(oldtok[1], self.filename,
1144                                             0, "typedef")
1145
1146                 else:
1147                     token = self.token()
1148         return token
1149
1150      #
1151      # Parse a C struct definition till the balancing }
1152      #
1153     def parseStruct(self, token):
1154         fields = []
1155          #self.debug("start parseStruct", token)
1156         while token != None:
1157             if token[0] == "sep" and token[1] == "{":
1158                 token = self.token()
1159                 token = self.parseTypeBlock(token)
1160             elif token[0] == "sep" and token[1] == "}":
1161                 self.struct_fields = fields
1162                  #self.debug("end parseStruct", token)
1163                  #print fields
1164                 token = self.token()
1165                 return token
1166             else:
1167                 base_type = self.type
1168                  #self.debug("before parseType", token)
1169                 token = self.parseType(token)
1170                  #self.debug("after parseType", token)
1171                 if token != None and token[0] == "name":
1172                     fname = token[1]
1173                     token = self.token()
1174                     if token[0] == "sep" and token[1] == ";":
1175                         self.comment = None
1176                         token = self.token()
1177                         fields.append((self.type, fname, self.comment))
1178                         self.comment = None
1179                     else:
1180                         self.error("parseStruct: expecting ;", token)
1181                 elif token != None and token[0] == "sep" and token[1] == "{":
1182                     token = self.token()
1183                     token = self.parseTypeBlock(token)
1184                     if token != None and token[0] == "name":
1185                         token = self.token()
1186                     if token != None and token[0] == "sep" and token[1] == ";":
1187                         token = self.token()
1188                     else:
1189                         self.error("parseStruct: expecting ;", token)
1190                 else:
1191                     self.error("parseStruct: name", token)
1192                     token = self.token()
1193                 self.type = base_type;
1194         self.struct_fields = fields
1195          #self.debug("end parseStruct", token)
1196          #print fields
1197         return token
1198
1199      #
1200      # Parse a C enum block, parse till the balancing }
1201      #
1202     def parseEnumBlock(self, token):
1203         self.enums = []
1204         name = None
1205         self.comment = None
1206         comment = ""
1207         value = "0"
1208         while token != None:
1209             if token[0] == "sep" and token[1] == "{":
1210                 token = self.token()
1211                 token = self.parseTypeBlock(token)
1212             elif token[0] == "sep" and token[1] == "}":
1213                 if name != None:
1214                     if self.comment != None:
1215                         comment = self.comment
1216                         self.comment = None
1217                     self.enums.append((name, value, comment))
1218                 token = self.token()
1219                 return token
1220             elif token[0] == "name":
1221                     if name != None:
1222                         if self.comment != None:
1223                             comment = self.comment.strip()
1224                             self.comment = None
1225                         self.enums.append((name, value, comment))
1226                     name = token[1]
1227                     comment = ""
1228                     token = self.token()
1229                     if token[0] == "op" and token[1][0] == "=":
1230                         value = ""
1231                         if len(token[1]) > 1:
1232                             value = token[1][1:]
1233                         token = self.token()
1234                         while token[0] != "sep" or (token[1] != ',' and
1235                               token[1] != '}'):
1236                             value = value + token[1]
1237                             token = self.token()
1238                     else:
1239                         try:
1240                             value = "%d" % (int(value) + 1)
1241                         except:
1242                             self.warning("Failed to compute value of enum %s" % (name))
1243                             value=""
1244                     if token[0] == "sep" and token[1] == ",":
1245                         token = self.token()
1246             else:
1247                 token = self.token()
1248         return token
1249
1250      #
1251      # Parse a C definition block, used for structs it parse till
1252      # the balancing }
1253      #
1254     def parseTypeBlock(self, token):
1255         while token != None:
1256             if token[0] == "sep" and token[1] == "{":
1257                 token = self.token()
1258                 token = self.parseTypeBlock(token)
1259             elif token[0] == "sep" and token[1] == "}":
1260                 token = self.token()
1261                 return token
1262             else:
1263                 token = self.token()
1264         return token
1265
1266      #
1267      # Parse a type: the fact that the type name can either occur after
1268      #    the definition or within the definition makes it a little harder
1269      #    if inside, the name token is pushed back before returning
1270      #
1271     def parseType(self, token):
1272         self.type = ""
1273         self.struct_fields = []
1274         self.signature = None
1275         if token == None:
1276             return token
1277
1278         while token[0] == "name" and (
1279               token[1] == "const" or \
1280               token[1] == "unsigned" or \
1281               token[1] == "signed"):
1282             if self.type == "":
1283                 self.type = token[1]
1284             else:
1285                 self.type = self.type + " " + token[1]
1286             token = self.token()
1287
1288         if token[0] == "name" and (token[1] == "long" or token[1] == "short"):
1289             if self.type == "":
1290                 self.type = token[1]
1291             else:
1292                 self.type = self.type + " " + token[1]
1293             if token[0] == "name" and token[1] == "int":
1294                 if self.type == "":
1295                     self.type = tmp[1]
1296                 else:
1297                     self.type = self.type + " " + tmp[1]
1298
1299         elif token[0] == "name" and token[1] == "struct":
1300             if self.type == "":
1301                 self.type = token[1]
1302             else:
1303                 self.type = self.type + " " + token[1]
1304             token = self.token()
1305             nametok = None
1306             if token[0] == "name":
1307                 nametok = token
1308                 token = self.token()
1309             if token != None and token[0] == "sep" and token[1] == "{":
1310                 token = self.token()
1311                 token = self.parseStruct(token)
1312             elif token != None and token[0] == "op" and token[1] == "*":
1313                 self.type = self.type + " " + nametok[1] + " *"
1314                 token = self.token()
1315                 while token != None and token[0] == "op" and token[1] == "*":
1316                     self.type = self.type + " *"
1317                     token = self.token()
1318                 if token[0] == "name":
1319                     nametok = token
1320                     token = self.token()
1321                 else:
1322                     self.error("struct : expecting name", token)
1323                     return token
1324             elif token != None and token[0] == "name" and nametok != None:
1325                 self.type = self.type + " " + nametok[1]
1326                 return token
1327
1328             if nametok != None:
1329                 self.lexer.push(token)
1330                 token = nametok
1331             return token
1332
1333         elif token[0] == "name" and token[1] == "enum":
1334             if self.type == "":
1335                 self.type = token[1]
1336             else:
1337                 self.type = self.type + " " + token[1]
1338             self.enums = []
1339             token = self.token()
1340             if token != None and token[0] == "sep" and token[1] == "{":
1341                 token = self.token()
1342                 token = self.parseEnumBlock(token)
1343             else:
1344                 self.error("parsing enum: expecting '{'", token)
1345             enum_type = None
1346             if token != None and token[0] != "name":
1347                 self.lexer.push(token)
1348                 token = ("name", "enum")
1349             else:
1350                 enum_type = token[1]
1351             for enum in self.enums:
1352                 self.index_add(enum[0], self.filename,
1353                                not self.is_header, "enum",
1354                                (enum[1], enum[2], enum_type))
1355             return token
1356
1357         elif token[0] == "name":
1358             if self.type == "":
1359                 self.type = token[1]
1360             else:
1361                 self.type = self.type + " " + token[1]
1362         else:
1363             self.error("parsing type %s: expecting a name" % (self.type),
1364                        token)
1365             return token
1366         token = self.token()
1367         while token != None and (token[0] == "op" or
1368               token[0] == "name" and token[1] == "const"):
1369             self.type = self.type + " " + token[1]
1370             token = self.token()
1371
1372          #
1373          # if there is a parenthesis here, this means a function type
1374          #
1375         if token != None and token[0] == "sep" and token[1] == '(':
1376             self.type = self.type + token[1]
1377             token = self.token()
1378             while token != None and token[0] == "op" and token[1] == '*':
1379                 self.type = self.type + token[1]
1380                 token = self.token()
1381             if token == None or token[0] != "name" :
1382                 self.error("parsing function type, name expected", token);
1383                 return token
1384             self.type = self.type + token[1]
1385             nametok = token
1386             token = self.token()
1387             if token != None and token[0] == "sep" and token[1] == ')':
1388                 self.type = self.type + token[1]
1389                 token = self.token()
1390                 if token != None and token[0] == "sep" and token[1] == '(':
1391                     token = self.token()
1392                     type = self.type;
1393                     token = self.parseSignature(token);
1394                     self.type = type;
1395                 else:
1396                     self.error("parsing function type, '(' expected", token);
1397                     return token
1398             else:
1399                 self.error("parsing function type, ')' expected", token);
1400                 return token
1401             self.lexer.push(token)
1402             token = nametok
1403             return token
1404
1405          #
1406          # do some lookahead for arrays
1407          #
1408         if token != None and token[0] == "name":
1409             nametok = token
1410             token = self.token()
1411             if token != None and token[0] == "sep" and token[1] == '[':
1412                 self.type = self.type + nametok[1]
1413                 while token != None and token[0] == "sep" and token[1] == '[':
1414                     self.type = self.type + token[1]
1415                     token = self.token()
1416                     while token != None and token[0] != 'sep' and \
1417                           token[1] != ']' and token[1] != ';':
1418                         self.type = self.type + token[1]
1419                         token = self.token()
1420                 if token != None and token[0] == 'sep' and token[1] == ']':
1421                     self.type = self.type + token[1]
1422                     token = self.token()
1423                 else:
1424                     self.error("parsing array type, ']' expected", token);
1425                     return token
1426             elif token != None and token[0] == "sep" and token[1] == ':':
1427                  # remove :12 in case it's a limited int size
1428                 token = self.token()
1429                 token = self.token()
1430             self.lexer.push(token)
1431             token = nametok
1432
1433         return token
1434
1435      #
1436      # Parse a signature: '(' has been parsed and we scan the type definition
1437      #    up to the ')' included
1438     def parseSignature(self, token):
1439         signature = []
1440         if token != None and token[0] == "sep" and token[1] == ')':
1441             self.signature = []
1442             token = self.token()
1443             return token
1444         while token != None:
1445             token = self.parseType(token)
1446             if token != None and token[0] == "name":
1447                 signature.append((self.type, token[1], None))
1448                 token = self.token()
1449             elif token != None and token[0] == "sep" and token[1] == ',':
1450                 token = self.token()
1451                 continue
1452             elif token != None and token[0] == "sep" and token[1] == ')':
1453                  # only the type was provided
1454                 if self.type == "...":
1455                     signature.append((self.type, "...", None))
1456                 else:
1457                     signature.append((self.type, None, None))
1458             if token != None and token[0] == "sep":
1459                 if token[1] == ',':
1460                     token = self.token()
1461                     continue
1462                 elif token[1] == ')':
1463                     token = self.token()
1464                     break
1465         self.signature = signature
1466         return token
1467
1468      #
1469      # Parse a global definition, be it a type, variable or function
1470      # the extern "C" blocks are a bit nasty and require it to recurse.
1471      #
1472     def parseGlobal(self, token):
1473         static = 0
1474         if token[1] == 'extern':
1475             token = self.token()
1476             if token == None:
1477                 return token
1478             if token[0] == 'string':
1479                 if token[1] == 'C':
1480                     token = self.token()
1481                     if token == None:
1482                         return token
1483                     if token[0] == 'sep' and token[1] == "{":
1484                         token = self.token()
1485 #                         print 'Entering extern "C line ', self.lineno()
1486                         while token != None and (token[0] != 'sep' or
1487                               token[1] != "}"):
1488                             if token[0] == 'name':
1489                                 token = self.parseGlobal(token)
1490                             else:
1491                                 self.error(
1492                                  "token %s %s unexpected at the top level" % (
1493                                         token[0], token[1]))
1494                                 token = self.parseGlobal(token)
1495 #                         print 'Exiting extern "C" line', self.lineno()
1496                         token = self.token()
1497                         return token
1498                 else:
1499                     return token
1500         elif token[1] == 'static':
1501             static = 1
1502             token = self.token()
1503             if token == None or  token[0] != 'name':
1504                 return token
1505
1506         if token[1] == 'typedef':
1507             token = self.token()
1508             return self.parseTypedef(token)
1509         else:
1510             token = self.parseType(token)
1511             type_orig = self.type
1512         if token == None or token[0] != "name":
1513             return token
1514         type = type_orig
1515         self.name = token[1]
1516         token = self.token()
1517         while token != None and (token[0] == "sep" or token[0] == "op"):
1518             if token[0] == "sep":
1519                 if token[1] == "[":
1520                     type = type + token[1]
1521                     token = self.token()
1522                     while token != None and (token[0] != "sep" or \
1523                           token[1] != ";"):
1524                         type = type + token[1]
1525                         token = self.token()
1526
1527             if token != None and token[0] == "op" and token[1] == "=":
1528                  #
1529                  # Skip the initialization of the variable
1530                  #
1531                 token = self.token()
1532                 if token[0] == 'sep' and token[1] == '{':
1533                     token = self.token()
1534                     token = self.parseBlock(token)
1535                 else:
1536                     self.comment = None
1537                     while token != None and (token[0] != "sep" or \
1538                           (token[1] != ';' and token[1] != ',')):
1539                             token = self.token()
1540                 self.comment = None
1541                 if token == None or token[0] != "sep" or (token[1] != ';' and
1542                    token[1] != ','):
1543                     self.error("missing ';' or ',' after value")
1544
1545             if token != None and token[0] == "sep":
1546                 if token[1] == ";":
1547                     self.comment = None
1548                     token = self.token()
1549                     if type == "struct":
1550                         self.index_add(self.name, self.filename,
1551                              not self.is_header, "struct", self.struct_fields)
1552                     else:
1553                         self.index_add(self.name, self.filename,
1554                              not self.is_header, "variable", type)
1555                     break
1556                 elif token[1] == "(":
1557                     token = self.token()
1558                     token = self.parseSignature(token)
1559                     if token == None:
1560                         return None
1561                     if token[0] == "sep" and token[1] == ";":
1562                         d = self.mergeFunctionComment(self.name,
1563                                 ((type, None), self.signature), 1)
1564                         self.index_add(self.name, self.filename, static,
1565                                         "function", d)
1566                         token = self.token()
1567                     elif token[0] == "sep" and token[1] == "{":
1568                         d = self.mergeFunctionComment(self.name,
1569                                 ((type, None), self.signature), static)
1570                         self.index_add(self.name, self.filename, static,
1571                                         "function", d)
1572                         token = self.token()
1573                         token = self.parseBlock(token);
1574                 elif token[1] == ',':
1575                     self.comment = None
1576                     self.index_add(self.name, self.filename, static,
1577                                     "variable", type)
1578                     type = type_orig
1579                     token = self.token()
1580                     while token != None and token[0] == "sep":
1581                         type = type + token[1]
1582                         token = self.token()
1583                     if token != None and token[0] == "name":
1584                         self.name = token[1]
1585                         token = self.token()
1586                 else:
1587                     break
1588
1589         return token
1590
1591     def parse(self):
1592         self.warning("Parsing %s" % (self.filename))
1593         token = self.token()
1594         while token != None:
1595             if token[0] == 'name':
1596                 token = self.parseGlobal(token)
1597             else:
1598                 self.error("token %s %s unexpected at the top level" % (
1599                        token[0], token[1]))
1600                 token = self.parseGlobal(token)
1601                 return
1602         self.parseTopComment(self.top_comment)
1603         return self.index
1604
1605
1606 class docBuilder:
1607     """A documentation builder"""
1608     def __init__(self, name, directories=['.'], excludes=[]):
1609         self.name = name
1610         self.directories = directories
1611         self.excludes = excludes + list(ignored_files.keys())
1612         self.modules = {}
1613         self.headers = {}
1614         self.idx = index()
1615         self.xref = {}
1616         self.index = {}
1617         if name == 'libxml2':
1618             self.basename = 'libxml'
1619         else:
1620             self.basename = name
1621
1622     def indexString(self, id, str):
1623         if str == None:
1624             return
1625         str = str.replace("'", ' ')
1626         str = str.replace('"', ' ')
1627         str = str.replace("/", ' ')
1628         str = str.replace('*', ' ')
1629         str = str.replace("[", ' ')
1630         str = str.replace("]", ' ')
1631         str = str.replace("(", ' ')
1632         str = str.replace(")", ' ')
1633         str = str.replace("<", ' ')
1634         str = str.replace('>', ' ')
1635         str = str.replace("&", ' ')
1636         str = str.replace('#', ' ')
1637         str = str.replace(",", ' ')
1638         str = str.replace('.', ' ')
1639         str = str.replace(';', ' ')
1640         tokens = str.split()
1641         for token in tokens:
1642             try:
1643                 c = token[0]
1644                 if string.ascii_letters.find(c) < 0:
1645                     pass
1646                 elif len(token) < 3:
1647                     pass
1648                 else:
1649                     lower = token.lower()
1650                     # TODO: generalize this a bit
1651                     if lower == 'and' or lower == 'the':
1652                         pass
1653                     elif token in self.xref:
1654                         self.xref[token].append(id)
1655                     else:
1656                         self.xref[token] = [id]
1657             except:
1658                 pass
1659
1660     def analyze(self):
1661         print("Project %s : %d headers, %d modules" % (self.name, len(list(self.headers.keys())), len(list(self.modules.keys()))))
1662         self.idx.analyze()
1663
1664     def scanHeaders(self):
1665         for header in list(self.headers.keys()):
1666             parser = CParser(header)
1667             idx = parser.parse()
1668             self.headers[header] = idx;
1669             self.idx.merge(idx)
1670
1671     def scanModules(self):
1672         for module in list(self.modules.keys()):
1673             parser = CParser(module)
1674             idx = parser.parse()
1675             # idx.analyze()
1676             self.modules[module] = idx
1677             self.idx.merge_public(idx)
1678
1679     def scan(self):
1680         for directory in self.directories:
1681             files = glob.glob(directory + "/*.c")
1682             for file in files:
1683                 skip = 0
1684                 for excl in self.excludes:
1685                     if file.find(excl) != -1:
1686                         print("Skipping %s" % file)
1687                         skip = 1
1688                         break
1689                 if skip == 0:
1690                     self.modules[file] = None;
1691             files = glob.glob(directory + "/*.h")
1692             for file in files:
1693                 skip = 0
1694                 for excl in self.excludes:
1695                     if file.find(excl) != -1:
1696                         print("Skipping %s" % file)
1697                         skip = 1
1698                         break
1699                 if skip == 0:
1700                     self.headers[file] = None;
1701         self.scanHeaders()
1702         self.scanModules()
1703
1704     def modulename_file(self, file):
1705         module = os.path.basename(file)
1706         if module[-2:] == '.h':
1707             module = module[:-2]
1708         elif module[-2:] == '.c':
1709             module = module[:-2]
1710         return module
1711
1712     def serialize_enum(self, output, name):
1713         id = self.idx.enums[name]
1714         output.write("    <enum name='%s' file='%s'" % (name,
1715                      self.modulename_file(id.header)))
1716         if id.info != None:
1717             info = id.info
1718             if info[0] != None and info[0] != '':
1719                 try:
1720                     val = eval(info[0])
1721                 except:
1722                     val = info[0]
1723                 output.write(" value='%s'" % (val));
1724             if info[2] != None and info[2] != '':
1725                 output.write(" type='%s'" % info[2]);
1726             if info[1] != None and info[1] != '':
1727                 output.write(" info='%s'" % escape(info[1]));
1728         output.write("/>\n")
1729
1730     def serialize_macro(self, output, name):
1731         id = self.idx.macros[name]
1732         output.write("    <macro name='%s' file='%s'>\n" % (name,
1733                      self.modulename_file(id.header)))
1734         if id.info != None:
1735             try:
1736                 (args, desc) = id.info
1737                 if desc != None and desc != "":
1738                     output.write("      <info>%s</info>\n" % (escape(desc)))
1739                     self.indexString(name, desc)
1740                 for arg in args:
1741                     (name, desc) = arg
1742                     if desc != None and desc != "":
1743                         output.write("      <arg name='%s' info='%s'/>\n" % (
1744                                      name, escape(desc)))
1745                         self.indexString(name, desc)
1746                     else:
1747                         output.write("      <arg name='%s'/>\n" % (name))
1748             except:
1749                 pass
1750         output.write("    </macro>\n")
1751
1752     def serialize_typedef(self, output, name):
1753         id = self.idx.typedefs[name]
1754         if id.info[0:7] == 'struct ':
1755             output.write("    <struct name='%s' file='%s' type='%s'" % (
1756                      name, self.modulename_file(id.header), id.info))
1757             name = id.info[7:]
1758             if name in self.idx.structs and ( \
1759                type(self.idx.structs[name].info) == type(()) or
1760                 type(self.idx.structs[name].info) == type([])):
1761                 output.write(">\n");
1762                 try:
1763                     for field in self.idx.structs[name].info:
1764                         desc = field[2]
1765                         self.indexString(name, desc)
1766                         if desc == None:
1767                             desc = ''
1768                         else:
1769                             desc = escape(desc)
1770                         output.write("      <field name='%s' type='%s' info='%s'/>\n" % (field[1] , field[0], desc))
1771                 except:
1772                     print("Failed to serialize struct %s" % (name))
1773                 output.write("    </struct>\n")
1774             else:
1775                 output.write("/>\n");
1776         else :
1777             output.write("    <typedef name='%s' file='%s' type='%s'" % (
1778                          name, self.modulename_file(id.header), id.info))
1779             try:
1780                 desc = id.extra
1781                 if desc != None and desc != "":
1782                     output.write(">\n      <info>%s</info>\n" % (escape(desc)))
1783                     output.write("    </typedef>\n")
1784                 else:
1785                     output.write("/>\n")
1786             except:
1787                 output.write("/>\n")
1788
1789     def serialize_variable(self, output, name):
1790         id = self.idx.variables[name]
1791         if id.info != None:
1792             output.write("    <variable name='%s' file='%s' type='%s'/>\n" % (
1793                     name, self.modulename_file(id.header), id.info))
1794         else:
1795             output.write("    <variable name='%s' file='%s'/>\n" % (
1796                     name, self.modulename_file(id.header)))
1797
1798     def serialize_function(self, output, name):
1799         id = self.idx.functions[name]
1800         if name == debugsym:
1801             print("=>", id)
1802
1803         output.write("    <%s name='%s' file='%s' module='%s'>\n" % (id.type,
1804                      name, self.modulename_file(id.header),
1805                      self.modulename_file(id.module)))
1806         #
1807         # Processing of conditionals modified by Bill 1/1/05
1808         #
1809         if id.conditionals != None:
1810             apstr = ""
1811             for cond in id.conditionals:
1812                 if apstr != "":
1813                     apstr = apstr + " &amp;&amp; "
1814                 apstr = apstr + cond
1815             output.write("      <cond>%s</cond>\n"% (apstr));
1816         try:
1817             (ret, params, desc) = id.info
1818             if (desc == None or desc == '') and \
1819                name[0:9] != "xmlThrDef" and name != "xmlDllMain":
1820                 print("%s %s from %s has no description" % (id.type, name,
1821                        self.modulename_file(id.module)))
1822
1823             output.write("      <info>%s</info>\n" % (escape(desc)))
1824             self.indexString(name, desc)
1825             if ret[0] != None:
1826                 if ret[0] == "void":
1827                     output.write("      <return type='void'/>\n")
1828                 else:
1829                     output.write("      <return type='%s' info='%s'/>\n" % (
1830                              ret[0], escape(ret[1])))
1831                     self.indexString(name, ret[1])
1832             for param in params:
1833                 if param[0] == 'void':
1834                     continue
1835                 if param[2] == None:
1836                     output.write("      <arg name='%s' type='%s' info=''/>\n" % (param[1], param[0]))
1837                 else:
1838                     output.write("      <arg name='%s' type='%s' info='%s'/>\n" % (param[1], param[0], escape(param[2])))
1839                     self.indexString(name, param[2])
1840         except:
1841             print("Failed to save function %s info: " % name, repr(id.info))
1842         output.write("    </%s>\n" % (id.type))
1843
1844     def serialize_exports(self, output, file):
1845         module = self.modulename_file(file)
1846         output.write("    <file name='%s'>\n" % (module))
1847         dict = self.headers[file]
1848         if dict.info != None:
1849             for data in ('Summary', 'Description', 'Author'):
1850                 try:
1851                     output.write("     <%s>%s</%s>\n" % (
1852                                  data.lower(),
1853                                  escape(dict.info[data]),
1854                                  data.lower()))
1855                 except:
1856                     print("Header %s lacks a %s description" % (module, data))
1857             if 'Description' in dict.info:
1858                 desc = dict.info['Description']
1859                 if desc.find("DEPRECATED") != -1:
1860                     output.write("     <deprecated/>\n")
1861
1862         ids = list(dict.macros.keys())
1863         ids.sort()
1864         for id in uniq(ids):
1865             # Macros are sometime used to masquerade other types.
1866             if id in dict.functions:
1867                 continue
1868             if id in dict.variables:
1869                 continue
1870             if id in dict.typedefs:
1871                 continue
1872             if id in dict.structs:
1873                 continue
1874             if id in dict.enums:
1875                 continue
1876             output.write("     <exports symbol='%s' type='macro'/>\n" % (id))
1877         ids = list(dict.enums.keys())
1878         ids.sort()
1879         for id in uniq(ids):
1880             output.write("     <exports symbol='%s' type='enum'/>\n" % (id))
1881         ids = list(dict.typedefs.keys())
1882         ids.sort()
1883         for id in uniq(ids):
1884             output.write("     <exports symbol='%s' type='typedef'/>\n" % (id))
1885         ids = list(dict.structs.keys())
1886         ids.sort()
1887         for id in uniq(ids):
1888             output.write("     <exports symbol='%s' type='struct'/>\n" % (id))
1889         ids = list(dict.variables.keys())
1890         ids.sort()
1891         for id in uniq(ids):
1892             output.write("     <exports symbol='%s' type='variable'/>\n" % (id))
1893         ids = list(dict.functions.keys())
1894         ids.sort()
1895         for id in uniq(ids):
1896             output.write("     <exports symbol='%s' type='function'/>\n" % (id))
1897         output.write("    </file>\n")
1898
1899     def serialize_xrefs_files(self, output):
1900         headers = list(self.headers.keys())
1901         headers.sort()
1902         for file in headers:
1903             module = self.modulename_file(file)
1904             output.write("    <file name='%s'>\n" % (module))
1905             dict = self.headers[file]
1906             ids = uniq(list(dict.functions.keys()) + list(dict.variables.keys()) + \
1907                   list(dict.macros.keys()) + list(dict.typedefs.keys()) + \
1908                   list(dict.structs.keys()) + list(dict.enums.keys()))
1909             ids.sort()
1910             for id in ids:
1911                 output.write("      <ref name='%s'/>\n" % (id))
1912             output.write("    </file>\n")
1913         pass
1914
1915     def serialize_xrefs_functions(self, output):
1916         funcs = {}
1917         for name in list(self.idx.functions.keys()):
1918             id = self.idx.functions[name]
1919             try:
1920                 (ret, params, desc) = id.info
1921                 for param in params:
1922                     if param[0] == 'void':
1923                         continue
1924                     if param[0] in funcs:
1925                         funcs[param[0]].append(name)
1926                     else:
1927                         funcs[param[0]] = [name]
1928             except:
1929                 pass
1930         typ = list(funcs.keys())
1931         typ.sort()
1932         for type in typ:
1933             if type == '' or type == 'void' or type == "int" or \
1934                type == "char *" or type == "const char *" :
1935                 continue
1936             output.write("    <type name='%s'>\n" % (type))
1937             ids = funcs[type]
1938             ids.sort()
1939             pid = ''        # not sure why we have dups, but get rid of them!
1940             for id in ids:
1941                 if id != pid:
1942                     output.write("      <ref name='%s'/>\n" % (id))
1943                     pid = id
1944             output.write("    </type>\n")
1945
1946     def serialize_xrefs_constructors(self, output):
1947         funcs = {}
1948         for name in list(self.idx.functions.keys()):
1949             id = self.idx.functions[name]
1950             try:
1951                 (ret, params, desc) = id.info
1952                 if ret[0] == "void":
1953                     continue
1954                 if ret[0] in funcs:
1955                     funcs[ret[0]].append(name)
1956                 else:
1957                     funcs[ret[0]] = [name]
1958             except:
1959                 pass
1960         typ = list(funcs.keys())
1961         typ.sort()
1962         for type in typ:
1963             if type == '' or type == 'void' or type == "int" or \
1964                type == "char *" or type == "const char *" :
1965                 continue
1966             output.write("    <type name='%s'>\n" % (type))
1967             ids = funcs[type]
1968             ids.sort()
1969             for id in ids:
1970                 output.write("      <ref name='%s'/>\n" % (id))
1971             output.write("    </type>\n")
1972
1973     def serialize_xrefs_alpha(self, output):
1974         letter = None
1975         ids = list(self.idx.identifiers.keys())
1976         ids.sort()
1977         for id in ids:
1978             if id[0] != letter:
1979                 if letter != None:
1980                     output.write("    </letter>\n")
1981                 letter = id[0]
1982                 output.write("    <letter name='%s'>\n" % (letter))
1983             output.write("      <ref name='%s'/>\n" % (id))
1984         if letter != None:
1985             output.write("    </letter>\n")
1986
1987     def serialize_xrefs_references(self, output):
1988         typ = list(self.idx.identifiers.keys())
1989         typ.sort()
1990         for id in typ:
1991             idf = self.idx.identifiers[id]
1992             module = idf.header
1993             output.write("    <reference name='%s' href='%s'/>\n" % (id,
1994                          'html/' + self.basename + '-' +
1995                          self.modulename_file(module) + '.html#' +
1996                          id))
1997
1998     def serialize_xrefs_index(self, output):
1999         index = self.xref
2000         typ = list(index.keys())
2001         typ.sort()
2002         letter = None
2003         count = 0
2004         chunk = 0
2005         chunks = []
2006         for id in typ:
2007             if len(index[id]) > 30:
2008                 continue
2009             if id[0] != letter:
2010                 if letter == None or count > 200:
2011                     if letter != None:
2012                         output.write("      </letter>\n")
2013                         output.write("    </chunk>\n")
2014                         count = 0
2015                         chunks.append(["chunk%s" % (chunk -1), first_letter, letter])
2016                     output.write("    <chunk name='chunk%s'>\n" % (chunk))
2017                     first_letter = id[0]
2018                     chunk = chunk + 1
2019                 elif letter != None:
2020                     output.write("      </letter>\n")
2021                 letter = id[0]
2022                 output.write("      <letter name='%s'>\n" % (letter))
2023             output.write("        <word name='%s'>\n" % (id))
2024             tokens = index[id];
2025             tokens.sort()
2026             tok = None
2027             for token in tokens:
2028                 if tok == token:
2029                     continue
2030                 tok = token
2031                 output.write("          <ref name='%s'/>\n" % (token))
2032                 count = count + 1
2033             output.write("        </word>\n")
2034         if letter != None:
2035             output.write("      </letter>\n")
2036             output.write("    </chunk>\n")
2037             if count != 0:
2038                 chunks.append(["chunk%s" % (chunk -1), first_letter, letter])
2039             output.write("    <chunks>\n")
2040             for ch in chunks:
2041                 output.write("      <chunk name='%s' start='%s' end='%s'/>\n" % (
2042                              ch[0], ch[1], ch[2]))
2043             output.write("    </chunks>\n")
2044
2045     def serialize_xrefs(self, output):
2046         output.write("  <references>\n")
2047         self.serialize_xrefs_references(output)
2048         output.write("  </references>\n")
2049         output.write("  <alpha>\n")
2050         self.serialize_xrefs_alpha(output)
2051         output.write("  </alpha>\n")
2052         output.write("  <constructors>\n")
2053         self.serialize_xrefs_constructors(output)
2054         output.write("  </constructors>\n")
2055         output.write("  <functions>\n")
2056         self.serialize_xrefs_functions(output)
2057         output.write("  </functions>\n")
2058         output.write("  <files>\n")
2059         self.serialize_xrefs_files(output)
2060         output.write("  </files>\n")
2061         output.write("  <index>\n")
2062         self.serialize_xrefs_index(output)
2063         output.write("  </index>\n")
2064
2065     def serialize(self):
2066         filename = "%s-api.xml" % self.name
2067         print("Saving XML description %s" % (filename))
2068         output = open(filename, "w")
2069         output.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
2070         output.write("<api name='%s'>\n" % self.name)
2071         output.write("  <files>\n")
2072         headers = list(self.headers.keys())
2073         headers.sort()
2074         for file in headers:
2075             self.serialize_exports(output, file)
2076         output.write("  </files>\n")
2077         output.write("  <symbols>\n")
2078         macros = list(self.idx.macros.keys())
2079         macros.sort()
2080         for macro in macros:
2081             self.serialize_macro(output, macro)
2082         enums = list(self.idx.enums.keys())
2083         enums.sort()
2084         for enum in enums:
2085             self.serialize_enum(output, enum)
2086         typedefs = list(self.idx.typedefs.keys())
2087         typedefs.sort()
2088         for typedef in typedefs:
2089             self.serialize_typedef(output, typedef)
2090         variables = list(self.idx.variables.keys())
2091         variables.sort()
2092         for variable in variables:
2093             self.serialize_variable(output, variable)
2094         functions = list(self.idx.functions.keys())
2095         functions.sort()
2096         for function in functions:
2097             self.serialize_function(output, function)
2098         output.write("  </symbols>\n")
2099         output.write("</api>\n")
2100         output.close()
2101
2102         filename = "%s-refs.xml" % self.name
2103         print("Saving XML Cross References %s" % (filename))
2104         output = open(filename, "w")
2105         output.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
2106         output.write("<apirefs name='%s'>\n" % self.name)
2107         self.serialize_xrefs(output)
2108         output.write("</apirefs>\n")
2109         output.close()
2110
2111
2112 def rebuild():
2113     builder = None
2114     if glob.glob("parser.c") != [] :
2115         print("Rebuilding API description for libxml2")
2116         builder = docBuilder("libxml2", [".", "."],
2117                              ["xmlwin32version.h", "tst.c"])
2118     elif glob.glob("../parser.c") != [] :
2119         print("Rebuilding API description for libxml2")
2120         builder = docBuilder("libxml2", ["..", "../include/libxml"],
2121                              ["xmlwin32version.h", "tst.c"])
2122     elif glob.glob("../libxslt/transform.c") != [] :
2123         print("Rebuilding API description for libxslt")
2124         builder = docBuilder("libxslt", ["../libxslt"],
2125                              ["win32config.h", "libxslt.h", "tst.c"])
2126     else:
2127         print("rebuild() failed, unable to guess the module")
2128         return None
2129     builder.scan()
2130     builder.analyze()
2131     builder.serialize()
2132     if glob.glob("../libexslt/exslt.c") != [] :
2133         extra = docBuilder("libexslt", ["../libexslt"], ["libexslt.h"])
2134         extra.scan()
2135         extra.analyze()
2136         extra.serialize()
2137     return builder
2138
2139 #
2140 # for debugging the parser
2141 #
2142 def parse(filename):
2143     parser = CParser(filename)
2144     idx = parser.parse()
2145     return idx
2146
2147 if __name__ == "__main__":
2148     if len(sys.argv) > 1:
2149         debug = 1
2150         parse(sys.argv[1])
2151     else:
2152         rebuild()