| #!/usr/bin/python |
| # |
| # Copyright (C) 2009 Chia-I Wu <olv@0xlab.org> |
| # |
| # Permission is hereby granted, free of charge, to any person obtaining a |
| # copy of this software and associated documentation files (the "Software"), |
| # to deal in the Software without restriction, including without limitation |
| # on the rights to use, copy, modify, merge, publish, distribute, sub |
| # license, and/or sell copies of the Software, and to permit persons to whom |
| # the Software is furnished to do so, subject to the following conditions: |
| # |
| # The above copyright notice and this permission notice (including the next |
| # paragraph) shall be included in all copies or substantial portions of the |
| # Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
| # IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| # IN THE SOFTWARE. |
| |
| import sys |
| import os.path |
| import getopt |
| import re |
| |
| GLAPI = "../../glapi" |
| sys.path.append(GLAPI) |
| |
| class HeaderParser(object): |
| """Parser for GL header files.""" |
| |
| def __init__(self, verbose=0): |
| # match #if and #ifdef |
| self.IFDEF = re.compile('#\s*if(n?def\s+(?P<ifdef>\w+)|\s+(?P<if>.+))') |
| # match #endif |
| self.ENDIF = re.compile('#\s*endif') |
| # match typedef abc def; |
| self.TYPEDEF = re.compile('typedef\s+(?P<from>[\w ]+)\s+(?P<to>\w+);') |
| # match #define XYZ VAL |
| self.DEFINE = re.compile('#\s*define\s+(?P<key>\w+)(?P<value>\s+[\w"]*)?') |
| # match GLAPI |
| 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*;') |
| |
| self.split_params = re.compile('\s*,\s*') |
| self.split_ctype = re.compile('(\W)') |
| # ignore GL_VERSION_X_Y |
| self.ignore_enum = re.compile('GL(_ES)?_VERSION(_ES_C[ML])?_\d_\d') |
| |
| self.verbose = verbose |
| self._reset() |
| |
| def _reset(self): |
| """Reset to initial state.""" |
| self.ifdef_levels = [] |
| self.need_char = False |
| |
| # use typeexpr? |
| def _format_ctype(self, ctype, fix=True): |
| """Format a ctype string, optionally fix it.""" |
| # split the type string |
| tmp = self.split_ctype.split(ctype) |
| tmp = [s for s in tmp if s and s != " "] |
| |
| pretty = "" |
| for i in xrange(len(tmp)): |
| # add missing GL prefix |
| if (fix and tmp[i] != "const" and tmp[i] != "*" and |
| not tmp[i].startswith("GL")): |
| tmp[i] = "GL" + tmp[i] |
| |
| if i == 0: |
| pretty = tmp[i] |
| else: |
| sep = " " |
| if tmp[i - 1] == "*": |
| sep = "" |
| pretty += sep + tmp[i] |
| return pretty |
| |
| # use typeexpr? |
| def _get_ctype_attrs(self, ctype): |
| """Get the attributes of a ctype.""" |
| is_float = (ctype.find("float") != -1 or ctype.find("double") != -1) |
| is_signed = not (ctype.find("unsigned") != -1) |
| |
| size = 0 |
| if ctype.find("char") != -1: |
| size = 1 |
| elif ctype.find("short") != -1: |
| size = 2 |
| elif ctype.find("int") != -1: |
| size = 4 |
| elif is_float: |
| if ctype.find("float") != -1: |
| size = 4 |
| else: |
| size = 8 |
| |
| return (size, is_float, is_signed) |
| |
| def _parse_define(self, line): |
| """Parse a #define line for an <enum>.""" |
| m = self.DEFINE.search(line) |
| if not m: |
| if self.verbose and line.find("#define") >= 0: |
| print "ignore %s" % (line) |
| return None |
| |
| key = m.group("key").strip() |
| val = m.group("value").strip() |
| |
| # enum must begin with GL_ and be all uppercase |
| if ((not (key.startswith("GL_") and key.isupper())) or |
| (self.ignore_enum.match(key) and val == "1")): |
| if self.verbose: |
| print "ignore enum %s" % (key) |
| return None |
| |
| return (key, val) |
| |
| def _parse_typedef(self, line): |
| """Parse a typedef line for a <type>.""" |
| m = self.TYPEDEF.search(line) |
| if not m: |
| if self.verbose and line.find("typedef") >= 0: |
| print "ignore %s" % (line) |
| return None |
| |
| f = m.group("from").strip() |
| t = m.group("to").strip() |
| if not t.startswith("GL"): |
| if self.verbose: |
| print "ignore type %s" % (t) |
| return None |
| attrs = self._get_ctype_attrs(f) |
| |
| return (f, t, attrs) |
| |
| def _parse_gl_api(self, line): |
| """Parse a GLAPI line for a <function>.""" |
| m = self.GLAPI.search(line) |
| if not m: |
| if self.verbose and line.find("APIENTRY") >= 0: |
| print "ignore %s" % (line) |
| return None |
| |
| rettype = m.group("return") |
| rettype = self._format_ctype(rettype) |
| if rettype == "GLvoid": |
| rettype = "" |
| |
| name = m.group("name") |
| |
| param_str = m.group("params") |
| chunks = self.split_params.split(param_str) |
| chunks = [s.strip() for s in chunks] |
| if len(chunks) == 1 and (chunks[0] == "void" or chunks[0] == "GLvoid"): |
| chunks = [] |
| |
| params = [] |
| for c in chunks: |
| # split type and variable name |
| idx = c.rfind("*") |
| if idx < 0: |
| idx = c.rfind(" ") |
| if idx >= 0: |
| idx += 1 |
| ctype = c[:idx] |
| var = c[idx:] |
| else: |
| ctype = c |
| var = "unnamed" |
| |
| # convert array to pointer |
| idx = var.find("[") |
| if idx >= 0: |
| var = var[:idx] |
| ctype += "*" |
| |
| ctype = self._format_ctype(ctype) |
| var = var.strip() |
| |
| if not self.need_char and ctype.find("GLchar") >= 0: |
| self.need_char = True |
| |
| params.append((ctype, var)) |
| |
| return (rettype, name, params) |
| |
| def _change_level(self, line): |
| """Parse a #ifdef line and change level.""" |
| m = self.IFDEF.search(line) |
| if m: |
| ifdef = m.group("ifdef") |
| if not ifdef: |
| ifdef = m.group("if") |
| self.ifdef_levels.append(ifdef) |
| return True |
| m = self.ENDIF.search(line) |
| if m: |
| self.ifdef_levels.pop() |
| return True |
| return False |
| |
| def _read_header(self, header): |
| """Open a header file and read its contents.""" |
| lines = [] |
| try: |
| fp = open(header, "rb") |
| lines = fp.readlines() |
| fp.close() |
| except IOError, e: |
| print "failed to read %s: %s" % (header, e) |
| return lines |
| |
| def _cmp_enum(self, enum1, enum2): |
| """Compare two enums.""" |
| # sort by length of the values as strings |
| val1 = enum1[1] |
| val2 = enum2[1] |
| ret = len(val1) - len(val2) |
| # sort by the values |
| if not ret: |
| val1 = int(val1, 16) |
| val2 = int(val2, 16) |
| ret = val1 - val2 |
| # in case int cannot hold the result |
| if ret > 0: |
| ret = 1 |
| elif ret < 0: |
| ret = -1 |
| # sort by the names |
| if not ret: |
| if enum1[0] < enum2[0]: |
| ret = -1 |
| elif enum1[0] > enum2[0]: |
| ret = 1 |
| return ret |
| |
| def _cmp_type(self, type1, type2): |
| """Compare two types.""" |
| attrs1 = type1[2] |
| attrs2 = type2[2] |
| # sort by type size |
| ret = attrs1[0] - attrs2[0] |
| # float is larger |
| if not ret: |
| ret = attrs1[1] - attrs2[1] |
| # signed is larger |
| if not ret: |
| ret = attrs1[2] - attrs2[2] |
| # reverse |
| ret = -ret |
| return ret |
| |
| def _cmp_function(self, func1, func2): |
| """Compare two functions.""" |
| name1 = func1[1] |
| name2 = func2[1] |
| ret = 0 |
| # sort by the names |
| if name1 < name2: |
| ret = -1 |
| elif name1 > name2: |
| ret = 1 |
| return ret |
| |
| def _postprocess_dict(self, hdict): |
| """Post-process a header dict and return an ordered list.""" |
| hlist = [] |
| largest = 0 |
| for key, cat in hdict.iteritems(): |
| size = len(cat["enums"]) + len(cat["types"]) + len(cat["functions"]) |
| # ignore empty category |
| if not size: |
| continue |
| |
| cat["enums"].sort(self._cmp_enum) |
| # remove duplicates |
| dup = [] |
| for i in xrange(1, len(cat["enums"])): |
| if cat["enums"][i] == cat["enums"][i - 1]: |
| dup.insert(0, i) |
| for i in dup: |
| e = cat["enums"].pop(i) |
| if self.verbose: |
| print "remove duplicate enum %s" % e[0] |
| |
| cat["types"].sort(self._cmp_type) |
| cat["functions"].sort(self._cmp_function) |
| |
| # largest category comes first |
| if size > largest: |
| hlist.insert(0, (key, cat)) |
| largest = size |
| else: |
| hlist.append((key, cat)) |
| return hlist |
| |
| def parse(self, header): |
| """Parse a header file.""" |
| self._reset() |
| |
| if self.verbose: |
| print "Parsing %s" % (header) |
| |
| hdict = {} |
| lines = self._read_header(header) |
| for line in lines: |
| if self._change_level(line): |
| continue |
| |
| # skip until the first ifdef (i.e. __gl_h_) |
| if not self.ifdef_levels: |
| continue |
| |
| cat_name = os.path.basename(header) |
| # check if we are in an extension |
| if (len(self.ifdef_levels) > 1 and |
| self.ifdef_levels[-1].startswith("GL_")): |
| cat_name = self.ifdef_levels[-1] |
| |
| try: |
| cat = hdict[cat_name] |
| except KeyError: |
| cat = { |
| "enums": [], |
| "types": [], |
| "functions": [] |
| } |
| hdict[cat_name] = cat |
| |
| key = "enums" |
| elem = self._parse_define(line) |
| if not elem: |
| key = "types" |
| elem = self._parse_typedef(line) |
| if not elem: |
| key = "functions" |
| elem = self._parse_gl_api(line) |
| |
| if elem: |
| cat[key].append(elem) |
| |
| if self.need_char: |
| if self.verbose: |
| print "define GLchar" |
| elem = self._parse_typedef("typedef char GLchar;") |
| cat["types"].append(elem) |
| return self._postprocess_dict(hdict) |
| |
| def spaces(n, str=""): |
| spaces = n - len(str) |
| if spaces < 1: |
| spaces = 1 |
| return " " * spaces |
| |
| def output_xml(name, hlist): |
| """Output a parsed header in OpenGLAPI XML.""" |
| |
| for i in xrange(len(hlist)): |
| cat_name, cat = hlist[i] |
| |
| print '<category name="%s">' % (cat_name) |
| indent = 4 |
| |
| for enum in cat["enums"]: |
| name = enum[0][3:] |
| value = enum[1] |
| tab = spaces(41, name) |
| attrs = 'name="%s"%svalue="%s"' % (name, tab, value) |
| print '%s<enum %s/>' % (spaces(indent), attrs) |
| |
| if cat["enums"] and cat["types"]: |
| print |
| |
| for type in cat["types"]: |
| ctype = type[0] |
| size, is_float, is_signed = type[2] |
| |
| attrs = 'name="%s"' % (type[1][2:]) |
| attrs += spaces(16, attrs) + 'size="%d"' % (size) |
| if is_float: |
| attrs += ' float="true"' |
| elif not is_signed: |
| attrs += ' unsigned="true"' |
| |
| print '%s<type %s/>' % (spaces(indent), attrs) |
| |
| for func in cat["functions"]: |
| print |
| ret = func[0] |
| name = func[1][2:] |
| params = func[2] |
| |
| attrs = 'name="%s" offset="assign"' % name |
| print '%s<function %s>' % (spaces(indent), attrs) |
| |
| for param in params: |
| attrs = 'name="%s" type="%s"' % (param[1], param[0]) |
| print '%s<param %s/>' % (spaces(indent * 2), attrs) |
| if ret: |
| attrs = 'type="%s"' % ret |
| print '%s<return %s/>' % (spaces(indent * 2), attrs) |
| |
| print '%s</function>' % spaces(indent) |
| |
| print '</category>' |
| print |
| |
| def show_usage(): |
| print "Usage: %s [-v] <header> ..." % sys.argv[0] |
| sys.exit(1) |
| |
| def main(): |
| try: |
| args, headers = getopt.getopt(sys.argv[1:], "v") |
| except Exception, e: |
| show_usage() |
| if not headers: |
| show_usage() |
| |
| verbose = 0 |
| for arg in args: |
| if arg[0] == "-v": |
| verbose += 1 |
| |
| need_xml_header = True |
| parser = HeaderParser(verbose) |
| for h in headers: |
| h = os.path.abspath(h) |
| hlist = parser.parse(h) |
| |
| if need_xml_header: |
| print '<?xml version="1.0"?>' |
| print '<!DOCTYPE OpenGLAPI SYSTEM "%s/gl_API.dtd">' % GLAPI |
| need_xml_header = False |
| |
| print |
| print '<!-- %s -->' % (h) |
| print '<OpenGLAPI>' |
| print |
| output_xml(h, hlist) |
| print '</OpenGLAPI>' |
| |
| if __name__ == '__main__': |
| main() |