Home | History | Annotate | Download | only in main
      1 #!/usr/bin/python
      2 #
      3 # Copyright (C) 2009 Chia-I Wu <olv (at] 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 A parser for APIspec.
     25 """
     26 
     27 class SpecError(Exception):
     28     """Error in the spec file."""
     29 
     30 
     31 class Spec(object):
     32     """A Spec is an abstraction of the API spec."""
     33 
     34     def __init__(self, doc):
     35         self.doc = doc
     36 
     37         self.spec_node = doc.getRootElement()
     38         self.tmpl_nodes = {}
     39         self.api_nodes = {}
     40         self.impl_node = None
     41 
     42         # parse <apispec>
     43         node = self.spec_node.children
     44         while node:
     45             if node.type == "element":
     46                 if node.name == "template":
     47                     self.tmpl_nodes[node.prop("name")] = node
     48                 elif node.name == "api":
     49                     self.api_nodes[node.prop("name")] = node
     50                 else:
     51                     raise SpecError("unexpected node %s in apispec" %
     52                             node.name)
     53             node = node.next
     54 
     55         # find an implementation
     56         for name, node in self.api_nodes.iteritems():
     57             if node.prop("implementation") == "true":
     58                 self.impl_node = node
     59                 break
     60         if not self.impl_node:
     61             raise SpecError("unable to find an implementation")
     62 
     63     def get_impl(self):
     64         """Return the implementation."""
     65         return API(self, self.impl_node)
     66 
     67     def get_api(self, name):
     68         """Return an API."""
     69         return API(self, self.api_nodes[name])
     70 
     71 
     72 class API(object):
     73     """An API consists of categories and functions."""
     74 
     75     def __init__(self, spec, api_node):
     76         self.name = api_node.prop("name")
     77         self.is_impl = (api_node.prop("implementation") == "true")
     78 
     79         self.categories = []
     80         self.functions = []
     81 
     82         # parse <api>
     83         func_nodes = []
     84         node = api_node.children
     85         while node:
     86             if node.type == "element":
     87                 if node.name == "category":
     88                     cat = node.prop("name")
     89                     self.categories.append(cat)
     90                 elif node.name == "function":
     91                     func_nodes.append(node)
     92                 else:
     93                     raise SpecError("unexpected node %s in api" % node.name)
     94             node = node.next
     95 
     96         # realize functions
     97         for func_node in func_nodes:
     98             tmpl_node = spec.tmpl_nodes[func_node.prop("template")]
     99             try:
    100                 func = Function(tmpl_node, func_node, self.is_impl,
    101                                 self.categories)
    102             except SpecError, e:
    103                 func_name = func_node.prop("name")
    104                 raise SpecError("failed to parse %s: %s" % (func_name, e))
    105             self.functions.append(func)
    106 
    107     def match(self, func, conversions={}):
    108         """Find a matching function in the API."""
    109         match = None
    110         need_conv = False
    111         for f in self.functions:
    112             matched, conv = f.match(func, conversions)
    113             if matched:
    114                 match = f
    115                 need_conv = conv
    116                 # exact match
    117                 if not need_conv:
    118                     break
    119         return (match, need_conv)
    120 
    121 
    122 class Function(object):
    123     """Parse and realize a <template> node."""
    124 
    125     def __init__(self, tmpl_node, func_node, force_skip_desc=False, categories=[]):
    126         self.tmpl_name = tmpl_node.prop("name")
    127         self.direction = tmpl_node.prop("direction")
    128 
    129         self.name = func_node.prop("name")
    130         self.prefix = func_node.prop("default_prefix")
    131         self.is_external = (func_node.prop("external") == "true")
    132 
    133         if force_skip_desc:
    134             self._skip_desc = True
    135         else:
    136             self._skip_desc = (func_node.prop("skip_desc") == "true")
    137 
    138         self._categories = categories
    139 
    140         # these attributes decide how the template is realized
    141         self._gltype = func_node.prop("gltype")
    142         if func_node.hasProp("vector_size"):
    143             self._vector_size = int(func_node.prop("vector_size"))
    144         else:
    145             self._vector_size = 0
    146         self._expand_vector = (func_node.prop("expand_vector") == "true")
    147 
    148         self.return_type = "void"
    149         param_nodes = []
    150 
    151         # find <proto>
    152         proto_node = tmpl_node.children
    153         while proto_node:
    154             if proto_node.type == "element" and proto_node.name == "proto":
    155                 break
    156             proto_node = proto_node.next
    157         if not proto_node:
    158             raise SpecError("no proto")
    159         # and parse it
    160         node = proto_node.children
    161         while node:
    162             if node.type == "element":
    163                 if node.name == "return":
    164                     self.return_type = node.prop("type")
    165                 elif node.name == "param" or node.name == "vector":
    166                     if self.support_node(node):
    167                         # make sure the node is not hidden
    168                         if not (self._expand_vector and
    169                                 (node.prop("hide_if_expanded") == "true")):
    170                             param_nodes.append(node)
    171                 else:
    172                     raise SpecError("unexpected node %s in proto" % node.name)
    173             node = node.next
    174 
    175         self._init_params(param_nodes)
    176         self._init_descs(tmpl_node, param_nodes)
    177 
    178     def __str__(self):
    179         return "%s %s%s(%s)" % (self.return_type, self.prefix, self.name,
    180                 self.param_string(True))
    181 
    182     def _init_params(self, param_nodes):
    183         """Parse and initialize parameters."""
    184         self.params = []
    185 
    186         for param_node in param_nodes:
    187             size = self.param_node_size(param_node)
    188             # when no expansion, vector is just like param
    189             if param_node.name == "param" or not self._expand_vector:
    190                 param = Parameter(param_node, self._gltype, size)
    191                 self.params.append(param)
    192                 continue
    193 
    194             if not size or size > param_node.lsCountNode():
    195                 raise SpecError("could not expand %s with unknown or "
    196                                 "mismatch sizes" % param.name)
    197 
    198             # expand the vector
    199             expanded_params = []
    200             child = param_node.children
    201             while child:
    202                 if (child.type == "element" and child.name == "param" and
    203                     self.support_node(child)):
    204                     expanded_params.append(Parameter(child, self._gltype))
    205                     if len(expanded_params) == size:
    206                         break
    207                 child = child.next
    208             # just in case that lsCountNode counts unknown nodes
    209             if len(expanded_params) < size:
    210                 raise SpecError("not enough named parameters")
    211 
    212             self.params.extend(expanded_params)
    213 
    214     def _init_descs(self, tmpl_node, param_nodes):
    215         """Parse and initialize parameter descriptions."""
    216         self.checker = Checker()
    217         if self._skip_desc:
    218             return
    219 
    220         node = tmpl_node.children
    221         while node:
    222             if node.type == "element" and node.name == "desc":
    223                 if self.support_node(node):
    224                     # parse <desc>
    225                     desc = Description(node, self._categories)
    226                     self.checker.add_desc(desc)
    227             node = node.next
    228 
    229         self.checker.validate(self, param_nodes)
    230 
    231     def support_node(self, node):
    232         """Return true if a node is in the supported category."""
    233         return (not node.hasProp("category") or
    234                 node.prop("category") in self._categories)
    235 
    236     def get_param(self, name):
    237         """Return the named parameter."""
    238         for param in self.params:
    239             if param.name == name:
    240                 return param
    241         return None
    242 
    243     def param_node_size(self, param):
    244         """Return the size of a vector."""
    245         if param.name != "vector":
    246             return 0
    247 
    248         size = param.prop("size")
    249         if size.isdigit():
    250             size = int(size)
    251         else:
    252             size = 0
    253         if not size:
    254             size = self._vector_size
    255             if not size and self._expand_vector:
    256                 # return the number of named parameters
    257                 size = param.lsCountNode()
    258         return size
    259 
    260     def param_string(self, declaration):
    261         """Return the C code of the parameters."""
    262         args = []
    263         if declaration:
    264             for param in self.params:
    265                 sep = "" if param.type.endswith("*") else " "
    266                 args.append("%s%s%s" % (param.type, sep, param.name))
    267             if not args:
    268                 args.append("void")
    269         else:
    270             for param in self.params:
    271                 args.append(param.name)
    272         return ", ".join(args)
    273 
    274     def match(self, other, conversions={}):
    275         """Return true if the functions match, probably with a conversion."""
    276         if (self.tmpl_name != other.tmpl_name or
    277             self.return_type != other.return_type or
    278             len(self.params) != len(other.params)):
    279             return (False, False)
    280 
    281         need_conv = False
    282         for i in xrange(len(self.params)):
    283             src = other.params[i]
    284             dst = self.params[i]
    285             if (src.is_vector != dst.is_vector or src.size != dst.size):
    286                 return (False, False)
    287             if src.type != dst.type:
    288                 if dst.base_type() in conversions.get(src.base_type(), []):
    289                     need_conv = True
    290                 else:
    291                     # unable to convert
    292                     return (False, False)
    293 
    294         return (True, need_conv)
    295 
    296 
    297 class Parameter(object):
    298     """A parameter of a function."""
    299 
    300     def __init__(self, param_node, gltype=None, size=0):
    301         self.is_vector = (param_node.name == "vector")
    302 
    303         self.name = param_node.prop("name")
    304         self.size = size
    305 
    306         type = param_node.prop("type")
    307         if gltype:
    308             type = type.replace("GLtype", gltype)
    309         elif type.find("GLtype") != -1:
    310             raise SpecError("parameter %s has unresolved type" % self.name)
    311 
    312         self.type = type
    313 
    314     def base_type(self):
    315         """Return the base GL type by stripping qualifiers."""
    316         return [t for t in self.type.split(" ") if t.startswith("GL")][0]
    317 
    318 
    319 class Checker(object):
    320     """A checker is the collection of all descriptions on the same level.
    321     Descriptions of the same parameter are concatenated.
    322     """
    323 
    324     def __init__(self):
    325         self.switches = {}
    326         self.switch_constants = {}
    327 
    328     def add_desc(self, desc):
    329         """Add a description."""
    330         # TODO allow index to vary
    331         const_attrs = ["index", "error", "convert", "size_str"]
    332         if desc.name not in self.switches:
    333             self.switches[desc.name] = []
    334             self.switch_constants[desc.name] = {}
    335             for attr in const_attrs:
    336                 self.switch_constants[desc.name][attr] = None
    337 
    338         # some attributes, like error code, should be the same for all descs
    339         consts = self.switch_constants[desc.name]
    340         for attr in const_attrs:
    341             if getattr(desc, attr) is not None:
    342                 if (consts[attr] is not None and
    343                     consts[attr] != getattr(desc, attr)):
    344                     raise SpecError("mismatch %s for %s" % (attr, desc.name))
    345                 consts[attr] = getattr(desc, attr)
    346 
    347         self.switches[desc.name].append(desc)
    348 
    349     def validate(self, func, param_nodes):
    350         """Validate the checker against a function."""
    351         tmp = Checker()
    352 
    353         for switch in self.switches.itervalues():
    354             valid_descs = []
    355             for desc in switch:
    356                 if desc.validate(func, param_nodes):
    357                     valid_descs.append(desc)
    358             # no possible values
    359             if not valid_descs:
    360                 return False
    361             for desc in valid_descs:
    362                 if not desc._is_noop:
    363                     tmp.add_desc(desc)
    364 
    365         self.switches = tmp.switches
    366         self.switch_constants = tmp.switch_constants
    367         return True
    368 
    369     def flatten(self, name=None):
    370         """Return a flat list of all descriptions of the named parameter."""
    371         flat_list = []
    372         for switch in self.switches.itervalues():
    373             for desc in switch:
    374                 if not name or desc.name == name:
    375                     flat_list.append(desc)
    376                 flat_list.extend(desc.checker.flatten(name))
    377         return flat_list
    378 
    379     def always_check(self, name):
    380         """Return true if the parameter is checked in all possible pathes."""
    381         if name in self.switches:
    382             return True
    383 
    384         # a param is always checked if any of the switch always checks it
    385         for switch in self.switches.itervalues():
    386             # a switch always checks it if all of the descs always check it
    387             always = True
    388             for desc in switch:
    389                 if not desc.checker.always_check(name):
    390                     always = False
    391                     break
    392             if always:
    393                 return True
    394         return False
    395 
    396     def _c_switch(self, name, indent="\t"):
    397         """Output C switch-statement for the named parameter, for debug."""
    398         switch = self.switches.get(name, [])
    399         # make sure there are valid values
    400         need_switch = False
    401         for desc in switch:
    402             if desc.values:
    403                 need_switch = True
    404         if not need_switch:
    405             return []
    406 
    407         stmts = []
    408         var = switch[0].name
    409         if switch[0].index >= 0:
    410             var += "[%d]" % switch[0].index
    411         stmts.append("switch (%s) { /* assume GLenum */" % var)
    412 
    413         for desc in switch:
    414             if desc.values:
    415                 for val in desc.values:
    416                     stmts.append("case %s:" % val)
    417                 for dep_name in desc.checker.switches.iterkeys():
    418                     dep_stmts = [indent + s for s in desc.checker._c_switch(dep_name, indent)]
    419                     stmts.extend(dep_stmts)
    420                 stmts.append(indent + "break;")
    421 
    422         stmts.append("default:")
    423         stmts.append(indent + "ON_ERROR(%s);" % switch[0].error);
    424         stmts.append(indent + "break;")
    425         stmts.append("}")
    426 
    427         return stmts
    428 
    429     def dump(self, indent="\t"):
    430         """Dump the descriptions in C code."""
    431         stmts = []
    432         for name in self.switches.iterkeys():
    433             c_switch = self._c_switch(name)
    434             print "\n".join(c_switch)
    435 
    436 
    437 class Description(object):
    438     """A description desribes a parameter and its relationship with other
    439     parameters.
    440     """
    441 
    442     def __init__(self, desc_node, categories=[]):
    443         self._categories = categories
    444         self._is_noop = False
    445 
    446         self.name = desc_node.prop("name")
    447         self.index = -1
    448 
    449         self.error = desc_node.prop("error") or "GL_INVALID_ENUM"
    450         # vector_size may be C code
    451         self.size_str = desc_node.prop("vector_size")
    452 
    453         self._has_enum = False
    454         self.values = []
    455         dep_nodes = []
    456 
    457         # parse <desc>
    458         valid_names = ["value", "range", "desc"]
    459         node = desc_node.children
    460         while node:
    461             if node.type == "element":
    462                 if node.name in valid_names:
    463                     # ignore nodes that require unsupported categories
    464                     if (node.prop("category") and
    465                         node.prop("category") not in self._categories):
    466                         node = node.next
    467                         continue
    468                 else:
    469                     raise SpecError("unexpected node %s in desc" % node.name)
    470 
    471                 if node.name == "value":
    472                     val = node.prop("name")
    473                     if not self._has_enum and val.startswith("GL_"):
    474                         self._has_enum = True
    475                     self.values.append(val)
    476                 elif node.name == "range":
    477                     first = int(node.prop("from"))
    478                     last = int(node.prop("to"))
    479                     base = node.prop("base") or ""
    480                     if not self._has_enum and base.startswith("GL_"):
    481                         self._has_enum = True
    482                     # expand range
    483                     for i in xrange(first, last + 1):
    484                         self.values.append("%s%d" % (base, i))
    485                 else: # dependent desc
    486                     dep_nodes.append(node)
    487             node = node.next
    488 
    489         # default to convert if there is no enum
    490         self.convert = not self._has_enum
    491         if desc_node.hasProp("convert"):
    492             self.convert = (desc_node.prop("convert") == "true")
    493 
    494         self._init_deps(dep_nodes)
    495 
    496     def _init_deps(self, dep_nodes):
    497         """Parse and initialize dependents."""
    498         self.checker = Checker()
    499 
    500         for dep_node in dep_nodes:
    501             # recursion!
    502             dep = Description(dep_node, self._categories)
    503             self.checker.add_desc(dep)
    504 
    505     def _search_param_node(self, param_nodes, name=None):
    506         """Search the template parameters for the named node."""
    507         param_node = None
    508         param_index = -1
    509 
    510         if not name:
    511             name = self.name
    512         for node in param_nodes:
    513             if name == node.prop("name"):
    514                 param_node = node
    515             elif node.name == "vector":
    516                 child = node.children
    517                 idx = 0
    518                 while child:
    519                     if child.type == "element" and child.name == "param":
    520                         if name == child.prop("name"):
    521                             param_node = node
    522                             param_index = idx
    523                             break
    524                         idx += 1
    525                     child = child.next
    526             if param_node:
    527                 break
    528         return (param_node, param_index)
    529 
    530     def _find_final(self, func, param_nodes):
    531         """Find the final parameter."""
    532         param = func.get_param(self.name)
    533         param_index = -1
    534 
    535         # the described param is not in the final function
    536         if not param:
    537             # search the template parameters
    538             node, index = self._search_param_node(param_nodes)
    539             if not node:
    540                 raise SpecError("invalid desc %s in %s" %
    541                         (self.name, func.name))
    542 
    543             # a named parameter of a vector
    544             if index >= 0:
    545                 param = func.get_param(node.prop("name"))
    546                 param_index = index
    547             elif node.name == "vector":
    548                 # must be an expanded vector, check its size
    549                 if self.size_str and self.size_str.isdigit():
    550                     size = int(self.size_str)
    551                     expanded_size = func.param_node_size(node)
    552                     if size != expanded_size:
    553                         return (False, None, -1)
    554             # otherwise, it is a valid, but no-op, description
    555 
    556         return (True, param, param_index)
    557 
    558     def validate(self, func, param_nodes):
    559         """Validate a description against certain function."""
    560         if self.checker.switches and not self.values:
    561             raise SpecError("no valid values for %s" % self.name)
    562 
    563         valid, param, param_index = self._find_final(func, param_nodes)
    564         if not valid:
    565             return False
    566 
    567         # the description is valid, but the param is gone
    568         # mark it no-op so that it will be skipped
    569         if not param:
    570             self._is_noop = True
    571             return True
    572 
    573         if param.is_vector:
    574             # if param was known, this should have been done in __init__
    575             if self._has_enum:
    576                 self.size_str = "1"
    577             # size mismatch
    578             if (param.size and self.size_str and self.size_str.isdigit() and
    579                 param.size != int(self.size_str)):
    580                 return False
    581         elif self.size_str:
    582             # only vector accepts vector_size
    583             raise SpecError("vector_size is invalid for %s" % param.name)
    584 
    585         if not self.checker.validate(func, param_nodes):
    586             return False
    587 
    588         # update the description
    589         self.name = param.name
    590         self.index = param_index
    591 
    592         return True
    593 
    594 
    595 def main():
    596     import libxml2
    597 
    598     filename = "APIspec.xml"
    599     apinames = ["GLES1.1", "GLES2.0"]
    600 
    601     doc = libxml2.readFile(filename, None,
    602             libxml2.XML_PARSE_DTDLOAD +
    603             libxml2.XML_PARSE_DTDVALID +
    604             libxml2.XML_PARSE_NOBLANKS)
    605 
    606     spec = Spec(doc)
    607     impl = spec.get_impl()
    608     for apiname in apinames:
    609         spec.get_api(apiname)
    610 
    611     doc.freeDoc()
    612 
    613     print "%s is successfully parsed" % filename
    614 
    615 
    616 if __name__ == "__main__":
    617     main()
    618