Tizen 2.0 Release
[profile/ivi/osmesa.git] / src / mapi / glapi / gen-es / gl_parse_header.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the "Software"),
7 # to deal in the Software without restriction, including without limitation
8 # on the rights to use, copy, modify, merge, publish, distribute, sub
9 # license, and/or sell copies of the Software, and to permit persons to whom
10 # the Software is furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice (including the next
13 # paragraph) shall be included in all copies or substantial portions of the
14 # Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
19 # IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 # IN THE SOFTWARE.
23
24 import sys
25 import os.path
26 import getopt
27 import re
28
29 GLAPI = "../../glapi/gen"
30 sys.path.append(GLAPI)
31
32 class HeaderParser(object):
33     """Parser for GL header files."""
34
35     def __init__(self, verbose=0):
36         # match #if and #ifdef
37         self.IFDEF = re.compile('#\s*if(n?def\s+(?P<ifdef>\w+)|\s+(?P<if>.+))')
38         # match #endif
39         self.ENDIF = re.compile('#\s*endif')
40         # match typedef abc def;
41         self.TYPEDEF = re.compile('typedef\s+(?P<from>[\w ]+)\s+(?P<to>\w+);')
42         # match #define XYZ VAL
43         self.DEFINE = re.compile('#\s*define\s+(?P<key>\w+)(?P<value>\s+[\w"]*)?')
44         # match GLAPI
45         self.GLAPI = re.compile('^GL_?API(CALL)?\s+(?P<return>[\w\s*]+[\w*])\s+(GL)?_?APIENTRY\s+(?P<name>\w+)\s*\((?P<params>[\w\s(,*\[\])]+)\)\s*;')
46
47         self.split_params = re.compile('\s*,\s*')
48         self.split_ctype = re.compile('(\W)')
49         # ignore GL_VERSION_X_Y
50         self.ignore_enum = re.compile('GL(_ES)?_VERSION(_ES_C[ML])?_\d_\d')
51
52         self.verbose = verbose
53         self._reset()
54
55     def _reset(self):
56         """Reset to initial state."""
57         self.ifdef_levels = []
58         self.need_char = False
59
60     # use typeexpr?
61     def _format_ctype(self, ctype, fix=True):
62         """Format a ctype string, optionally fix it."""
63         # split the type string
64         tmp = self.split_ctype.split(ctype)
65         tmp = [s for s in tmp if s and s != " "]
66
67         pretty = ""
68         for i in xrange(len(tmp)):
69             # add missing GL prefix
70             if (fix and tmp[i] != "const" and tmp[i] != "*" and
71                 not tmp[i].startswith("GL")):
72                 tmp[i] = "GL" + tmp[i]
73
74             if i == 0:
75                 pretty = tmp[i]
76             else:
77                 sep = " "
78                 if tmp[i - 1] == "*":
79                     sep = ""
80                 pretty += sep + tmp[i]
81         return pretty
82
83     # use typeexpr?
84     def _get_ctype_attrs(self, ctype):
85         """Get the attributes of a ctype."""
86         is_float = (ctype.find("float") != -1 or ctype.find("double") != -1)
87         is_signed = not (ctype.find("unsigned")  != -1)
88
89         size = 0
90         if ctype.find("char") != -1:
91             size = 1
92         elif ctype.find("short") != -1:
93             size = 2
94         elif ctype.find("int") != -1:
95             size = 4
96         elif is_float:
97             if ctype.find("float") != -1:
98                 size = 4
99             else:
100                 size = 8
101
102         return (size, is_float, is_signed)
103
104     def _parse_define(self, line):
105         """Parse a #define line for an <enum>."""
106         m = self.DEFINE.search(line)
107         if not m:
108             if self.verbose and line.find("#define") >= 0:
109                 print "ignore %s" % (line)
110             return None
111
112         key = m.group("key").strip()
113         val = m.group("value").strip()
114
115         # enum must begin with GL_ and be all uppercase
116         if ((not (key.startswith("GL_") and key.isupper())) or
117             (self.ignore_enum.match(key) and val == "1")):
118             if self.verbose:
119                 print "ignore enum %s" % (key)
120             return None
121
122         return (key, val)
123
124     def _parse_typedef(self, line):
125         """Parse a typedef line for a <type>."""
126         m = self.TYPEDEF.search(line)
127         if not m:
128             if self.verbose and line.find("typedef") >= 0:
129                 print "ignore %s" % (line)
130             return None
131
132         f = m.group("from").strip()
133         t = m.group("to").strip()
134         if not t.startswith("GL"):
135             if self.verbose:
136                 print "ignore type %s" % (t)
137             return None
138         attrs = self._get_ctype_attrs(f)
139
140         return (f, t, attrs)
141
142     def _parse_gl_api(self, line):
143         """Parse a GLAPI line for a <function>."""
144         m = self.GLAPI.search(line)
145         if not m:
146             if self.verbose and line.find("APIENTRY") >= 0:
147                 print "ignore %s" % (line)
148             return None
149
150         rettype = m.group("return")
151         rettype = self._format_ctype(rettype)
152         if rettype == "GLvoid":
153             rettype = ""
154
155         name = m.group("name")
156
157         param_str = m.group("params")
158         chunks = self.split_params.split(param_str)
159         chunks = [s.strip() for s in chunks]
160         if len(chunks) == 1 and (chunks[0] == "void" or chunks[0] == "GLvoid"):
161             chunks = []
162
163         params = []
164         for c in chunks:
165             # split type and variable name
166             idx = c.rfind("*")
167             if idx < 0:
168                 idx = c.rfind(" ")
169             if idx >= 0:
170                 idx += 1
171                 ctype = c[:idx]
172                 var = c[idx:]
173             else:
174                 ctype = c
175                 var = "unnamed"
176
177             # convert array to pointer
178             idx = var.find("[")
179             if idx >= 0:
180                 var = var[:idx]
181                 ctype += "*"
182
183             ctype = self._format_ctype(ctype)
184             var = var.strip()
185
186             if not self.need_char and ctype.find("GLchar") >= 0:
187                 self.need_char = True
188
189             params.append((ctype, var))
190
191         return (rettype, name, params)
192
193     def _change_level(self, line):
194         """Parse a #ifdef line and change level."""
195         m = self.IFDEF.search(line)
196         if m:
197             ifdef = m.group("ifdef")
198             if not ifdef:
199                 ifdef = m.group("if")
200             self.ifdef_levels.append(ifdef)
201             return True
202         m = self.ENDIF.search(line)
203         if m:
204             self.ifdef_levels.pop()
205             return True
206         return False
207
208     def _read_header(self, header):
209         """Open a header file and read its contents."""
210         lines = []
211         try:
212             fp = open(header, "rb")
213             lines = fp.readlines()
214             fp.close()
215         except IOError, e:
216             print "failed to read %s: %s" % (header, e)
217         return lines
218
219     def _cmp_enum(self, enum1, enum2):
220         """Compare two enums."""
221         # sort by length of the values as strings
222         val1 = enum1[1]
223         val2 = enum2[1]
224         ret = len(val1) - len(val2)
225         # sort by the values
226         if not ret:
227             val1 = int(val1, 16)
228             val2 = int(val2, 16)
229             ret = val1 - val2
230             # in case int cannot hold the result
231             if ret > 0:
232                 ret = 1
233             elif ret < 0:
234                 ret = -1
235         # sort by the names
236         if not ret:
237             if enum1[0] < enum2[0]:
238                 ret = -1
239             elif enum1[0] > enum2[0]:
240                 ret = 1
241         return ret
242
243     def _cmp_type(self, type1, type2):
244         """Compare two types."""
245         attrs1 = type1[2]
246         attrs2 = type2[2]
247         # sort by type size
248         ret = attrs1[0] - attrs2[0]
249         # float is larger
250         if not ret:
251             ret = attrs1[1] - attrs2[1]
252         # signed is larger
253         if not ret:
254             ret = attrs1[2] - attrs2[2]
255         # reverse
256         ret = -ret
257         return ret
258
259     def _cmp_function(self, func1, func2):
260         """Compare two functions."""
261         name1 = func1[1]
262         name2 = func2[1]
263         ret = 0
264         # sort by the names
265         if name1 < name2:
266             ret = -1
267         elif name1 > name2:
268             ret = 1
269         return ret
270
271     def _postprocess_dict(self, hdict):
272         """Post-process a header dict and return an ordered list."""
273         hlist = []
274         largest = 0
275         for key, cat in hdict.iteritems():
276             size = len(cat["enums"]) + len(cat["types"]) + len(cat["functions"])
277             # ignore empty category
278             if not size:
279                 continue
280
281             cat["enums"].sort(self._cmp_enum)
282             # remove duplicates
283             dup = []
284             for i in xrange(1, len(cat["enums"])):
285                 if cat["enums"][i] == cat["enums"][i - 1]:
286                     dup.insert(0, i)
287             for i in dup:
288                 e = cat["enums"].pop(i)
289                 if self.verbose:
290                     print "remove duplicate enum %s" % e[0]
291
292             cat["types"].sort(self._cmp_type)
293             cat["functions"].sort(self._cmp_function)
294
295             # largest category comes first
296             if size > largest:
297                 hlist.insert(0, (key, cat))
298                 largest = size
299             else:
300                 hlist.append((key, cat))
301         return hlist
302
303     def parse(self, header):
304         """Parse a header file."""
305         self._reset()
306
307         if self.verbose:
308             print "Parsing %s" % (header)
309
310         hdict = {}
311         lines = self._read_header(header)
312         for line in lines:
313             if self._change_level(line):
314                 continue
315
316             # skip until the first ifdef (i.e. __gl_h_)
317             if not self.ifdef_levels:
318                 continue
319
320             cat_name = os.path.basename(header)
321             # check if we are in an extension
322             if (len(self.ifdef_levels) > 1 and
323                 self.ifdef_levels[-1].startswith("GL_")):
324                 cat_name = self.ifdef_levels[-1]
325
326             try:
327                 cat = hdict[cat_name]
328             except KeyError:
329                 cat = {
330                         "enums": [],
331                         "types": [],
332                         "functions": []
333                 }
334                 hdict[cat_name] = cat
335
336             key = "enums"
337             elem = self._parse_define(line)
338             if not elem:
339                 key = "types"
340                 elem = self._parse_typedef(line)
341             if not elem:
342                 key = "functions"
343                 elem = self._parse_gl_api(line)
344
345             if elem:
346                 cat[key].append(elem)
347
348         if self.need_char:
349             if self.verbose:
350                 print "define GLchar"
351             elem = self._parse_typedef("typedef char GLchar;")
352             cat["types"].append(elem)
353         return self._postprocess_dict(hdict)
354
355 def spaces(n, str=""):
356     spaces = n - len(str)
357     if spaces < 1:
358         spaces = 1
359     return " " * spaces
360
361 def output_xml(name, hlist):
362     """Output a parsed header in OpenGLAPI XML."""
363
364     for i in xrange(len(hlist)):
365         cat_name, cat = hlist[i]
366
367         print '<category name="%s">' % (cat_name)
368         indent = 4
369
370         for enum in cat["enums"]:
371             name = enum[0][3:]
372             value = enum[1]
373             tab = spaces(41, name)
374             attrs = 'name="%s"%svalue="%s"' % (name, tab, value)
375             print '%s<enum %s/>' % (spaces(indent), attrs)
376
377         if cat["enums"] and cat["types"]:
378             print
379
380         for type in cat["types"]:
381             ctype = type[0]
382             size, is_float, is_signed = type[2]
383
384             attrs = 'name="%s"' % (type[1][2:])
385             attrs += spaces(16, attrs) + 'size="%d"' % (size)
386             if is_float:
387                 attrs += ' float="true"'
388             elif not is_signed:
389                 attrs += ' unsigned="true"'
390
391             print '%s<type %s/>' % (spaces(indent), attrs)
392
393         for func in cat["functions"]:
394             print
395             ret = func[0]
396             name = func[1][2:]
397             params = func[2]
398
399             attrs = 'name="%s" offset="assign"' % name
400             print '%s<function %s>' % (spaces(indent), attrs)
401
402             for param in params:
403                 attrs = 'name="%s" type="%s"' % (param[1], param[0])
404                 print '%s<param %s/>' % (spaces(indent * 2), attrs)
405             if ret:
406                 attrs = 'type="%s"' % ret
407                 print '%s<return %s/>' % (spaces(indent * 2), attrs)
408
409             print '%s</function>' % spaces(indent)
410
411         print '</category>'
412         print
413
414 def show_usage():
415     print "Usage: %s [-v] <header> ..." % sys.argv[0]
416     sys.exit(1)
417
418 def main():
419     try:
420         args, headers = getopt.getopt(sys.argv[1:], "v")
421     except Exception, e:
422         show_usage()
423     if not headers:
424         show_usage()
425
426     verbose = 0
427     for arg in args:
428         if arg[0] == "-v":
429             verbose += 1
430
431     need_xml_header = True
432     parser = HeaderParser(verbose)
433     for h in headers:
434         h = os.path.abspath(h)
435         hlist = parser.parse(h)
436
437         if need_xml_header:
438             print '<?xml version="1.0"?>'
439             print '<!DOCTYPE OpenGLAPI SYSTEM "%s/gl_API.dtd">' % GLAPI
440             need_xml_header = False
441
442         print
443         print '<!-- %s -->' % (h)
444         print '<OpenGLAPI>'
445         print
446         output_xml(h, hlist)
447         print '</OpenGLAPI>'
448
449 if __name__ == '__main__':
450     main()