3 # Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
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:
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
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
29 GLAPI = "../../glapi/gen"
30 sys.path.append(GLAPI)
32 class HeaderParser(object):
33 """Parser for GL header files."""
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>.+))')
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"]*)?')
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*;')
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')
52 self.verbose = verbose
56 """Reset to initial state."""
57 self.ifdef_levels = []
58 self.need_char = False
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 != " "]
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]
80 pretty += sep + tmp[i]
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)
90 if ctype.find("char") != -1:
92 elif ctype.find("short") != -1:
94 elif ctype.find("int") != -1:
97 if ctype.find("float") != -1:
102 return (size, is_float, is_signed)
104 def _parse_define(self, line):
105 """Parse a #define line for an <enum>."""
106 m = self.DEFINE.search(line)
108 if self.verbose and line.find("#define") >= 0:
109 print "ignore %s" % (line)
112 key = m.group("key").strip()
113 val = m.group("value").strip()
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")):
119 print "ignore enum %s" % (key)
124 def _parse_typedef(self, line):
125 """Parse a typedef line for a <type>."""
126 m = self.TYPEDEF.search(line)
128 if self.verbose and line.find("typedef") >= 0:
129 print "ignore %s" % (line)
132 f = m.group("from").strip()
133 t = m.group("to").strip()
134 if not t.startswith("GL"):
136 print "ignore type %s" % (t)
138 attrs = self._get_ctype_attrs(f)
142 def _parse_gl_api(self, line):
143 """Parse a GLAPI line for a <function>."""
144 m = self.GLAPI.search(line)
146 if self.verbose and line.find("APIENTRY") >= 0:
147 print "ignore %s" % (line)
150 rettype = m.group("return")
151 rettype = self._format_ctype(rettype)
152 if rettype == "GLvoid":
155 name = m.group("name")
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"):
165 # split type and variable name
177 # convert array to pointer
183 ctype = self._format_ctype(ctype)
186 if not self.need_char and ctype.find("GLchar") >= 0:
187 self.need_char = True
189 params.append((ctype, var))
191 return (rettype, name, params)
193 def _change_level(self, line):
194 """Parse a #ifdef line and change level."""
195 m = self.IFDEF.search(line)
197 ifdef = m.group("ifdef")
199 ifdef = m.group("if")
200 self.ifdef_levels.append(ifdef)
202 m = self.ENDIF.search(line)
204 self.ifdef_levels.pop()
208 def _read_header(self, header):
209 """Open a header file and read its contents."""
212 fp = open(header, "rb")
213 lines = fp.readlines()
216 print "failed to read %s: %s" % (header, e)
219 def _cmp_enum(self, enum1, enum2):
220 """Compare two enums."""
221 # sort by length of the values as strings
224 ret = len(val1) - len(val2)
230 # in case int cannot hold the result
237 if enum1[0] < enum2[0]:
239 elif enum1[0] > enum2[0]:
243 def _cmp_type(self, type1, type2):
244 """Compare two types."""
248 ret = attrs1[0] - attrs2[0]
251 ret = attrs1[1] - attrs2[1]
254 ret = attrs1[2] - attrs2[2]
259 def _cmp_function(self, func1, func2):
260 """Compare two functions."""
271 def _postprocess_dict(self, hdict):
272 """Post-process a header dict and return an ordered list."""
275 for key, cat in hdict.iteritems():
276 size = len(cat["enums"]) + len(cat["types"]) + len(cat["functions"])
277 # ignore empty category
281 cat["enums"].sort(self._cmp_enum)
284 for i in xrange(1, len(cat["enums"])):
285 if cat["enums"][i] == cat["enums"][i - 1]:
288 e = cat["enums"].pop(i)
290 print "remove duplicate enum %s" % e[0]
292 cat["types"].sort(self._cmp_type)
293 cat["functions"].sort(self._cmp_function)
295 # largest category comes first
297 hlist.insert(0, (key, cat))
300 hlist.append((key, cat))
303 def parse(self, header):
304 """Parse a header file."""
308 print "Parsing %s" % (header)
311 lines = self._read_header(header)
313 if self._change_level(line):
316 # skip until the first ifdef (i.e. __gl_h_)
317 if not self.ifdef_levels:
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]
327 cat = hdict[cat_name]
334 hdict[cat_name] = cat
337 elem = self._parse_define(line)
340 elem = self._parse_typedef(line)
343 elem = self._parse_gl_api(line)
346 cat[key].append(elem)
350 print "define GLchar"
351 elem = self._parse_typedef("typedef char GLchar;")
352 cat["types"].append(elem)
353 return self._postprocess_dict(hdict)
355 def spaces(n, str=""):
356 spaces = n - len(str)
361 def output_xml(name, hlist):
362 """Output a parsed header in OpenGLAPI XML."""
364 for i in xrange(len(hlist)):
365 cat_name, cat = hlist[i]
367 print '<category name="%s">' % (cat_name)
370 for enum in cat["enums"]:
373 tab = spaces(41, name)
374 attrs = 'name="%s"%svalue="%s"' % (name, tab, value)
375 print '%s<enum %s/>' % (spaces(indent), attrs)
377 if cat["enums"] and cat["types"]:
380 for type in cat["types"]:
382 size, is_float, is_signed = type[2]
384 attrs = 'name="%s"' % (type[1][2:])
385 attrs += spaces(16, attrs) + 'size="%d"' % (size)
387 attrs += ' float="true"'
389 attrs += ' unsigned="true"'
391 print '%s<type %s/>' % (spaces(indent), attrs)
393 for func in cat["functions"]:
399 attrs = 'name="%s" offset="assign"' % name
400 print '%s<function %s>' % (spaces(indent), attrs)
403 attrs = 'name="%s" type="%s"' % (param[1], param[0])
404 print '%s<param %s/>' % (spaces(indent * 2), attrs)
406 attrs = 'type="%s"' % ret
407 print '%s<return %s/>' % (spaces(indent * 2), attrs)
409 print '%s</function>' % spaces(indent)
415 print "Usage: %s [-v] <header> ..." % sys.argv[0]
420 args, headers = getopt.getopt(sys.argv[1:], "v")
431 need_xml_header = True
432 parser = HeaderParser(verbose)
434 h = os.path.abspath(h)
435 hlist = parser.parse(h)
438 print '<?xml version="1.0"?>'
439 print '<!DOCTYPE OpenGLAPI SYSTEM "%s/gl_API.dtd">' % GLAPI
440 need_xml_header = False
443 print '<!-- %s -->' % (h)
449 if __name__ == '__main__':