# Copyright 2014 Intel Corporation # # 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 # the rights to use, copy, modify, merge, publish, distribute, sublicense, # 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 NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS 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. """ Parse gl.xml into Python objects. """ from __future__ import print_function import os.path import re import sys from collections import namedtuple from copy import copy, deepcopy # Export 'debug' so other Piglit modules can easily enable it. debug = False def _log_debug(msg): if debug: print('debug: {0}: {1}'.format(__name__, msg), file=sys.stderr) # Prefer the external module 'lxml.etree' (it uses libxml2) over Python's # builtin 'xml.etree.ElementTree'. It's faster. try: import lxml.etree as etree _log_debug('etree is lxml.etree') except ImportError: import xml.etree.cElementTree as etree _log_debug('etree is xml.etree.cElementTree') # Define a Python 2.6 compatibility wrapper for ElementTree.iterfind. _etree_iterfind = None if hasattr(etree.ElementTree(), 'iterfind'): _etree_iterfind = lambda elem, match: elem.iterfind(match) _log_debug('_etree_iterfind wraps ElementTree.iterfind') else: _etree_iterfind = lambda elem, match: iter(elem.findall(match)) _log_debug(('_etree_iterfind wraps ElementTree.findall for ' 'Python 2.6 compatibility')) def parse(): """Parse gl.xml and return a Registry object.""" filename = os.path.join(os.path.dirname(__file__), 'gl.xml') xml_registry = etree.parse(filename).getroot() _repair_xml(xml_registry) return Registry(xml_registry) def _repair_xml(xml_registry): fixes = set(( 'GL_ALL_ATTRIB_BITS', 'glOcclusionQueryEventMaskAMD', 'enums_SGI_0x8000_0x80BF', 'enums_ARB_0x80000_0x80BF', 'gles2_GL_ACTIVE_PROGRAM_EXT', )) remove_queue = [] def defer_removal(parent, child): remove_queue.append((parent, child)) for enums in _etree_iterfind(xml_registry, './enums'): if ('GL_ALL_ATTRIB_BITS' in fixes and enums.get('group') == 'AttribMask'): # The XML defines GL_ALL_ATTRIB_BITS incorrectly with all bits # set (0xFFFFFFFF). From the GL_ARB_multisample spec, v5: # # In order to avoid incompatibility with GL implementations # that do not support SGIS_multisample, ALL_ATTRIB_BITS # does not include MULTISAMPLE_BIT_ARB. # enum = enums.find("./enum[@name='GL_ALL_ATTRIB_BITS']") enum.set('value', '0x000FFFFF') fixes.remove('GL_ALL_ATTRIB_BITS') continue if ('glOcclusionQueryEventMaskAMD' in fixes and enums.get('namespace') == 'OcclusionQueryEventMaskAMD'): # This tag's attributes are totally broken. enums.set('namespace', 'GL') enums.set('group', 'OcclusionQueryEventMaskAMD') enums.set('type', 'bitmask') fixes.remove('glOcclusionQueryEventMaskAMD') continue if ('enums_SGI_0x8000_0x80BF' in fixes and enums.get('vendor') == 'SGI' and enums.get('start') == '0x8000' and enums.get('end') == '0x80BF'): # This element is empty garbage that overlaps an ARB enum group # with the same range. defer_removal(xml_registry, enums) fixes.remove('enums_SGI_0x8000_0x80BF') continue if ('enums_ARB_0x80000_0x80BF' in fixes and enums.get('vendor') == 'ARB' and enums.get('group', None) is None and enums.get('start', None) is None): # This tag lacks 'start' and 'end' attributes. enums.set('start', '0x8000') enums.set('end', '0x80BF') fixes.remove('enums_ARB_0x80000_0x80BF') continue if ('gles2_GL_ACTIVE_PROGRAM_EXT' in fixes and enums.get('vendor') == 'ARB' and enums.get('start') <= '0x8259' and enums.get('end') >= '0x8259'): # GL_ACTIVE_PROGRAM_EXT has different numerical values in GL # (0x8B8D) and in GLES (0x8259). Remove the GLES value to avoid # redefinition collisions. bad_enum = enums.find(("./enum" "[@value='0x8259']" "[@name='GL_ACTIVE_PROGRAM_EXT']" "[@api='gles2']")) defer_removal(enums, bad_enum) fixes.remove('gles2_GL_ACTIVE_PROGRAM_EXT') continue for (parent, child) in remove_queue: parent.remove(child) if len(fixes) > 0: raise Exception('failed to apply some xml repairs: ' + ', '.join(map(repr, fixes))) class OrderedKeyedSet(object): """A set with keyed elements that preserves order of element insertion. Why waste words? Let's document the class with example code. Example: Cheese = namedtuple('Cheese', ('name', 'flavor')) cheeses = OrderedKeyedSet(key='name') cheeses.add(Cheese(name='cheddar', flavor='good')) cheeses.add(Cheese(name='gouda', flavor='smells like feet')) cheeses.add(Cheese(name='romano', flavor='awesome')) # Elements are retrievable by key. assert(cheeses['gouda'].flavor == 'smells like feet') # On key collision, the old element is removed. cheeses.add(Cheese(name='gouda', flavor='ok i guess')) assert(cheeses['gouda'].flavor == 'ok i guess') # The set preserves order of insertion. Replacement does not alter # order. assert(list(cheeses)[2].name == 'romano') # The set is iterable. for cheese in cheeses: print(cheese.name) # Yet another example... Bread = namedtuple('Bread', ('name', 'smell')) breads = OrderedKeyedSet(key='name') breads.add(Bread(name='como', smell='subtle') breads.add(Bread(name='sourdough', smell='pleasant')) # The set supports some common set operations, such as union. breads_and_cheeses = breads | cheeses assert(len(breads_and_cheeses) == len(breads) + len(cheeses)) """ def __init__(self, key, elems=()): """Create a new set with the given key. The given 'key' defines how to calculate each element's key value. If 'key' is a string, then each key value is defined to be `getattr(elem, key)`. If 'key' is a function, then the key value is `key(elem)`. """ # A linked list contains the set's items. Each list node is a 4-tuple # [prev, next, key, value]. The root node is permanent. root = [] root[:] = [root, root, None, None] self.__list_root = root # For quick retrieval, we map each key to its node. That is, each map # pair has form {key: [prev, next, key, value])}. self.__map = dict() if isinstance(key, str): self.__key_func = lambda elem: getattr(elem, key) else: self.__key_func = key for e in elems: self.add(e) def __or__(self, other): """Same as `union`.""" return self.union(other) def __contains__(self, key): return key in self.__map def __copy__(self): return OrderedKeyedSet(key=deepcopy(self.__key_func), elems=iter(self)) def __getitem__(self, key): return self.__map[key][3] def __iter__(self): return self.itervalues() def __len__(self): return len(self.__map) def __repr__(self): templ = '{self.__class__.__name__}({self.name!r})' return templ.format(self=self) def add(self, value): key = self.__key_func(value) node = self.__map.get(key, None) if node is not None: node[3] = value else: root = self.__list_root old_tail = root[0] new_tail = [old_tail, root, key, value] new_tail[0][1] = new_tail new_tail[1][0] = new_tail self.__map[key] = new_tail def clear(self): self.__map.clear() root = self.__list_root root[:] = [root, root, None, None] def extend(self, elems): for e in elems: self.add(e) def get(self, key, default): node = self.__map.get(key, None) if node is not None: return node[3] else: return default def iteritems(self): root = self.__list_root node = root[1] while node is not root: yield (node[2], node[3]) node = node[1] def iterkeys(self): return (i[0] for i in self.iteritems()) def itervalues(self): return (i[1] for i in self.iteritems()) def pop(self, key): node = self.__map.pop(key) node[0][1] = node[1] node[1][0] = node[0] return node[3] def sort_by_key(self): sorted_items = sorted(self.__map.iteritems(), cmp=lambda x, y: cmp(x[0], y[0])) self.clear() for item in sorted_items: self.add(item[1]) def sort_by_value(self): sorted_values = sorted(self.__map.itervalues()) self.clear() for value in sorted_values: self.add(value) def union(self, other): """Return the union of two sets as a new set. In the new set, all elements of the self set precede those of the other set. The order of elements in the new set preserves the order of the original sets. The new set's key function is copied from self. On key collisions, set y has precedence over x. """ u = copy(self) u.extend(other) return u class ImmutableOrderedKeyedSet(OrderedKeyedSet): def __init__(self, key, elems): self.__is_frozen = False OrderedKeyedSet.__init__(self, key=key, elems=elems) self.__is_frozen = True def add(self, value): if self.__is_frozen: raise ImmutableError else: OrderedKeyedSet.add(self, value) def pop(self, key): raise ImmutableError def clear(self): raise ImmutableError class ImmutableError: pass # Values that may appear in the XML attributes 'api' and 'supported'. VALID_APIS = frozenset(('gl', 'glcore', 'gles1', 'gles2')) class Registry(object): """The toplevel element. Attributes: features: An OrderedKeyedSet that contains a Feature for each subelement. extensions: An OrderedKeyedSet that contains an Extension for each subelement. commands: An OrderedKeyedSet that contains a Command for each subelement. command_alias_map: A CommandAliasMap that contains a CommandAliasSet for each equivalence class of commands. enum_groups: An OrderedKeyedSet that contains an EnumGroup for each subelement. enums: An OrderedKeyedSet that contains an Enum for each subelement. vendor_namespaces: A collection of all vendor prefixes and suffixes, such as "ARB", "EXT", "CHROMIUM", and "NV". """ def __init__(self, xml_registry): """Parse the element.""" assert(xml_registry.tag == 'registry') self.command_alias_map = CommandAliasMap() self.commands = OrderedKeyedSet(key='name') self.enum_groups = [] self.enums = OrderedKeyedSet(key='name') self.extensions = OrderedKeyedSet(key='name') self.features = OrderedKeyedSet(key='name') self.vendor_namespaces = set() for xml_command in _etree_iterfind(xml_registry, './commands/command'): command = Command(xml_command) self.commands.add(command) self.command_alias_map.add(command) for xml_enums in _etree_iterfind(xml_registry, './enums'): enum_group = EnumGroup(xml_enums) self.enum_groups.append(enum_group) for enum in enum_group.enums: self.enums.add(enum) for xml_feature in _etree_iterfind(xml_registry, './feature'): feature = Feature(xml_feature, command_map=self.commands, enum_map=self.enums) self.features.add(feature) for xml_ext in _etree_iterfind(xml_registry, './extensions/extension'): ext = Extension(xml_ext, command_map=self.commands, enum_map=self.enums) self.extensions.add(ext) self.vendor_namespaces.add(ext.vendor_namespace) self.vendor_namespaces.remove(None) class Feature(object): """A XML element. Attributes: name: The XML element's 'name' attribute. api: The XML element's 'api' attribute. version_str: The XML element's 'number' attribute. For example, "3.1". version_float: float(version_str) version_int: int(10 * version_float) requirements: A collection of Requirement for each Command and Enum this Feature requires. """ def __init__(self, xml_feature, command_map, enum_map): """Parse a element.""" # Example element: # # # # # # # # # # # ... # # # # # # # # # ... # assert(xml_feature.tag == 'feature') # Parse the tag's attributes. self.name = xml_feature.get('name') self.api = xml_feature.get('api') self.is_gles = self.name.startswith('GL_ES') self.version_str = xml_feature.get('number') self.version_float = float(self.version_str) self.version_int = int(10 * self.version_float) self.__parse_requirements(xml_feature, command_map, enum_map) assert(self.api in VALID_APIS) assert(len(self.requirements) > 0) def __cmp__(self, other): if self is other: return 0 # Sort features before extensions. if isinstance(other, Extension): return -1 # Sort GL before GLES. diff = cmp(self.is_gles, other.is_gles) if diff != 0: return diff return cmp(self.name, other.name) def __repr__(self): templ = '{self.__class__.__name__}({self.name!r})' return templ.format(self=self) def __parse_requirements(self, xml_feature, command_map, enum_map): """For each and under a , create a Requirement that links this Feature to a Command or Enum. """ self.requirements = set() def link(x): req = Requirement(provider=self, provided=x, apis=frozenset((self.api,))) self.requirements.add(req) x.requirements.add(req) for xml_cmd in _etree_iterfind(xml_feature, './require/command'): cmd = command_map[xml_cmd.get('name')] link(cmd) for xml_enum in _etree_iterfind(xml_feature, './require/enum'): enum = enum_map[xml_enum.get('name')] link(enum) class Extension(object): """An XML element. Attributes: name: The XML element's 'name' attribute. supported_apis: The set of api strings in the XML element's 'supported' attribute. For example, set('gl', 'glcore'). vendor_namespace: For example, "AMD". May be None. requirements: A collection of Requirement for each Command and Enum this Extension requires. """ __VENDOR_REGEX = re.compile(r'^GL_(?P[A-Z]+)_') RATIFIED_NAMESPACES = ('KHR', 'ARB', 'OES') def __init__(self, xml_extension, command_map, enum_map): """Parse an element.""" # Example element: # # # # # ... # # # ... # # assert(xml_extension.tag == 'extension') self.name = xml_extension.get('name') self.vendor_namespace = None match = Extension.__VENDOR_REGEX.match(self.name) if match is not None: groups = match.groupdict() self.vendor_namespace = groups.get('vendor_namespace', None) self.supported_apis = xml_extension.get('supported').split('|') self.supported_apis = frozenset(self.supported_apis) assert(self.supported_apis <= VALID_APIS) self.__parse_requirements(xml_extension, command_map, enum_map) def __cmp__(self, other): if self is other: return 0 # Sort features before extensions. if isinstance(other, Feature): return 1 # Sort ratified before unratified. diff = cmp(other.is_ratified, self.is_ratified) if diff != 0: return diff # Sort EXT before others. diff = cmp(other.vendor_namespace == 'EXT', self.vendor_namespace == 'EXT') if diff != 0: return diff return cmp(self.name, other.name) def __repr__(self): templ = '{self.__class__.__name__}(name={self.name!r})' return templ.format(self=self) @property def is_ratified(self): """True if the vendor namespace is one that traditionally requires ratification by Khronos. """ return self.vendor_namespace in self.RATIFIED_NAMESPACES def __parse_requirements(self, xml_extension, command_map, enum_map): """For each and under a , create a Requirement that links this Extension to a Command or Enum. """ self.requirements = set() def link(xml_require, x): api = xml_require.get('api', None) if api is not None: assert(api in self.supported_apis) apis = frozenset((api,)) else: apis = frozenset(self.supported_apis) req = Requirement(provider=self, provided=x, apis=apis) self.requirements.add(req) x.requirements.add(req) for xml_req in _etree_iterfind(xml_extension, './require'): for xml_cmd in _etree_iterfind(xml_req, './command'): cmd = command_map[xml_cmd.get('name')] link(xml_req, cmd) for xml_enum in _etree_iterfind(xml_req, './enum'): enum = enum_map[xml_enum.get('name')] link(xml_req, enum) class Requirement(object): """A XML element, which links a provider (Feature or Extension) to a provided (Command or Enum) for a set of apis. """ def __init__(self, provider, provided, apis): self.provider = provider self.provided = provided self.apis = frozenset(apis) def choose_if(condition, obj): if condition: return obj else: return None self.has_feature = isinstance(provider, Feature) self.has_extension = isinstance(provider, Extension) self.has_command = isinstance(provided, Command) self.has_enum = isinstance(provided, Enum) self.feature = choose_if(self.has_feature, self.provider) self.extension = choose_if(self.has_extension, self.provider) self.command = choose_if(self.has_command, self.provided) self.enum = choose_if(self.has_enum, self.provided) assert(self.has_feature + self.has_extension == 1) assert(self.has_command + self.has_enum == 1) assert(self.apis <= VALID_APIS) _log_debug('created {0}'.format(self)) def __cmp__(self, other): """Sort by 'provider', then by 'provided'.""" diff = cmp(self.provider, other.provider) if diff != 0: return diff diff = cmp(self.provided, other.provided) if diff != 0: return diff return 0 def __repr__(self): templ = ('{self.__class__.__name__}' '(provider={self.provider.name!r},' ' provided={self.provided.name!r},' ' apis={api_tuple})') return templ.format(self=self, api_tuple=tuple(self.apis)) class CommandParam(object): """A XML element at path command/param. Attributes: name c_type """ __PARAM_NAME_FIXES = {'near': 'hither', 'far': 'yon'} def __init__(self, xml_param, log=None): """Parse a element.""" # Example elements: # # const GLchar *name # GLsizei *length # GLint *values # GLenum shadertype # GLsync sync assert(xml_param.tag == 'param') self.name = xml_param.find('./name').text # Rename the parameter if its name is a reserved keyword in MSVC. self.name = self.__PARAM_NAME_FIXES.get(self.name, self.name) # Pare the C type. c_type_text = list(xml_param.itertext()) c_type_text.pop(-1) # Pop off the text from the subelement. c_type_text = (t.strip() for t in c_type_text) self.c_type = ' '.join(c_type_text).strip() _log_debug('parsed {0}'.format(self)) def __repr__(self): templ = ('{self.__class__.__name__}' '(name={self.name!r}, type={self.c_type!r})') return templ.format(self=self) class Command(object): """A XML element. Attributes: name: The XML element's 'name' attribute, which is also the function name. c_return_type: For example, "void *". alias: The XML element's 'alias' element. May be None. param_list: List of that contains a CommandParam for each subelement. requirements: A collection of each Requirement that exposes this Command. """ def __init__(self, xml_command): """Parse a element.""" # Example element: # # # void glTexSubImage2D # GLenum target # GLint level # GLint xoffset # GLint yoffset # GLsizei width # GLsizei height # GLenum format # GLenum type # const void *pixels # # # # assert(xml_command.tag == 'command') xml_proto = xml_command.find('./proto') self.name = xml_proto.find('./name').text _log_debug('start parsing Command(name={0!r})'.format(self.name)) self.requirements = set() self.__vendor_namespace = None # Parse the return type from the element. # # Example of a difficult element: # const GLubyte *glGetStringi c_return_type_text = list(xml_proto.itertext()) c_return_type_text.pop(-1) # Pop off the text from the subelement. c_return_type_text = (t.strip() for t in c_return_type_text) self.c_return_type = ' '.join(c_return_type_text).strip() # Parse alias info, if any. xml_alias = xml_command.find('./alias') if xml_alias is None: self.alias = None else: self.alias = xml_alias.get('name') self.param_list = [ CommandParam(xml_param) for xml_param in _etree_iterfind(xml_command, './param') ] _log_debug(('parsed {self.__class__.__name__}(' 'name={self.name!r}, ' 'alias={self.alias!r}, ' 'prototype={self.c_prototype!r})').format(self=self)) def __cmp__(self, other): return cmp(self.name, other.name) def __repr__(self): templ = '{self.__class__.__name__}({self.name!r})' return templ.format(self=self) @property def vendor_namespace(self): if self.__vendor_namespace is None: for req in self.__requirements: ext = req.extension if ext is None: continue if ext.vendor_namespace is None: continue if self.name.endswith('_' + ext.vendor_namespace): self.__vendor_namespace = ext.vendor_namespace return self.__vendor_namespace @property def c_prototype(self): """For example, "void glAccum(GLenum o, GLfloat value)".""" return '{self.c_return_type} {self.name}({self.c_named_param_list})'.format(self=self) @property def c_funcptr_typedef(self): """For example, "PFNGLACCUMROC" for glAccum.""" return 'PFN{0}PROC'.format(self.name).upper() @property def c_named_param_list(self): """For example, "GLenum op, GLfloat value" for glAccum.""" return ', '.join( '{param.c_type} {param.name}'.format(param=param) for param in self.param_list ) @property def c_unnamed_param_list(self): """For example, "GLenum, GLfloat" for glAccum.""" return ', '.join( param.c_type for param in self.param_list ) @property def c_untyped_param_list(self): """For example, "op, value" for glAccum.""" return ', '.join( param.name for param in self.param_list ) class CommandAliasSet(ImmutableOrderedKeyedSet): def __init__(self, commands): ImmutableOrderedKeyedSet.__init__(self, key='name', elems=sorted(commands)) self.__primary_command = None self.__requirements = None def __cmp__(self, other): return cmp(self.name, other.name) def __repr__(self): templ = '{self.__class__.__name__}({self.name!r})' return templ.format(self=self) @property def name(self): return self.primary_command.name @property def primary_command(self): """The set's first command when sorted by name.""" for command in self: return command @property def requirements(self): """A sorted iterator over each Requirement that exposes this CommandAliasSet. """ if self.__requirements is None: self.__requirements = sorted( req for command in self for req in command.requirements ) _log_debug('{0} sorted requirements: {1}'.format( self, self.__requirements)) return iter(self.__requirements) class CommandAliasMap(object): def __init__(self): self.__map = dict() self.__sorted_unique_values = None def __getitem__(self, command_name): return self.__map[command_name] def __iter__(self): """A sorted iterator over the map's unique CommandAliasSet values.""" if self.__sorted_unique_values is None: self.__sorted_unique_values = sorted(set(self.__map.itervalues())) return iter(self.__sorted_unique_values) def get(self, command_name, default): return self.__map.get(command_name, default) def add(self, command): assert(isinstance(command, Command)) _log_debug('adding command {0!r} to CommandAliasMap'.format(command.name)) name = command.name name_set = self.get(name, None) assert(self.__is_set_mapping_complete(name_set)) alias = command.alias alias_set = self.get(command.alias, None) assert(self.__is_set_mapping_complete(alias_set)) if name_set is alias_set and name_set is not None: return # After modifying the contained alias sets, the mapping will no longer # be sorted. self.__sorted_unique_values = None new_set_elems = set((command,)) if name_set is not None: new_set_elems.update(name_set) if alias_set is not None: new_set_elems.update(alias_set) new_set = CommandAliasSet(new_set_elems) for other_command in new_set: self.__map[other_command.name] = new_set if other_command.alias is not None: self.__map[other_command.alias] = new_set def __is_set_mapping_complete(self, alias_set): if alias_set is None: return True for command in alias_set: if self[command.name] is not alias_set: return False if command.alias is None: continue if self[command.alias] is not alias_set: return False return True class EnumGroup(object): """An element at path registry/enums. Attributes: name: The XML element's 'group' attribute. If the XML does not define 'group', then this class invents one. type: The XML element's 'type' attribute. If the XML does not define 'type', then this class invents one. start, end: The XML element's 'start' and 'end' attributes. Each may be None. enums: An OrderedKeyedSet of Enum that contains each subelement in this . """ # Each EnumGroup belongs to exactly one member of EnumGroup.TYPES. # # Some members in EnumGroup.TYPES are invented and not present in gl.xml. # The only enum type defined explicitly in gl.xml is "bitmask", which # occurs as . However, in gl.xml each block of # non-bitmask enums is introduced by a comment that describes the block's # "type", even if the tag lacks a 'type' attribute. (Thanks, # Khronos, for encoding data in XML comments rather than the XML itself). # EnumGroup.TYPES lists such implicit comment-only types, with invented # names, alongside the types explicitly defined by . TYPES = ( # Type 'default_namespace' is self-explanatory. It indicates the large # set of enums from 0x0000 to 0xffff that includes, for example, # GL_POINTS and GL_TEXTURE_2D. 'default_namespace', # Type 'bitmask' is self-explanatory. 'bitmask', # Type 'small_index' indicates a small namespace of non-bitmask enums. # As of Khronos revision 26792, 'small_index' groups generally contain # small numbers used for indexed access. 'small_index', # Type 'special' is used only for the group named "SpecialNumbers". The # group contains enums such as GL_FALSE, GL_ZERO, and GL_INVALID_INDEX. 'special', ) def __init__(self, xml_enums): """Parse an element.""" # Example of a bitmask group: # # # # # # # Example of a group that resides in OpenGL's default enum namespace: # # # # # # ... # # # Example of a non-bitmask group that resides outside OpenGL's default # enum namespace: # # # # # # ... # # self.name = xml_enums.get('group', None) _log_debug('start parsing {0}'.format(self)) self.type = xml_enums.get('type', None) self.start = xml_enums.get('start', None) self.end = xml_enums.get('end', None) self.enums = [] self.__invent_name_and_type() assert(self.name is not None) assert(self.type in self.TYPES) _log_debug('start parsing subelements of {0}'.format(self)) self.enums = OrderedKeyedSet(key='name') for xml_enum in _etree_iterfind(xml_enums, './enum'): self.enums.add(Enum(self, xml_enum)) _log_debug('parsed {0}'.format(self)) def __repr__(self): templ = '{self.__class__.__name__}({self.name!r})' return templ.format(self=self) def __invent_name_and_type(self): """If the XML didn't define a name or type, invent one.""" if self.name is None: assert(self.type is None) assert(self.start is not None) assert(self.end is not None) self.name = 'range_{self.start}_{self.end}'.format(self=self) self.type = 'default_namespace' elif self.type is None: self.type = 'small_index' elif self.name == 'SpecialNumbers': assert(self.type is None) self.type = 'special' class Enum(object): """An XML element. Attributes: name, api: The XML element's 'name' and 'api' attributes. str_value, c_num_literal: Equivalent attributes. The XML element's 'value' attribute. num_value: The long integer for str_value. requirements: A collection of each Requirement that exposes this Enum. """ def __init__(self, enum_group, xml_enum): """Parse an tag located at path registry/enums/enum.""" # Example element: # assert(isinstance(enum_group, EnumGroup)) assert(xml_enum.tag == 'enum') self.requirements = set() self.__vendor_namespace = None self.group = enum_group self.name = xml_enum.get('name') self.api = xml_enum.get('api') self.str_value = xml_enum.get('value') self.c_num_literal = self.str_value if '0x' in self.str_value.lower(): base = 16 else: base = 10 self.num_value = long(self.str_value, base) _log_debug('parsed {0}'.format(self)) def __repr__(self): templ = ('{self.__class__.__name__}' '(name={self.name!r},' ' value={self.str_value!r})') return templ.format(self=self) @property def vendor_namespace(self): if self.__vendor_namespace is None: for req in self.requirements: ext = req.extension if ext is None: continue if ext.vendor_namespace is None: continue if self.name.endswith('_' + ext.vendor_namespace): self.__vendor_namespace = ext.vendor_namespace return self.__vendor_namespace