Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  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()
  451.