[Piglit] [PATCH 2/6] dispatch: Generate piglit-dispatch from Khronos XML

Dylan Baker baker.dylan.c at gmail.com
Tue Jun 17 07:53:07 PDT 2014


I know we had a chance to talk yesterday, but it's still probably a good
idea to have my comments in writing on the list.

On Monday, June 16, 2014 09:04:58 AM Chad Versace wrote:

[snip]

> diff --git a/registry/__init__.py b/registry/__init__.py
> new file mode 100644
> index 0000000..e69de29
> diff --git a/registry/gl.py b/registry/gl.py
> new file mode 100644
> index 0000000..32adb10
> --- /dev/null
> +++ b/registry/gl.py
> @@ -0,0 +1,832 @@
> +# 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.
> +"""
> +
> +import os.path
> +import re
> +import sys
> +import xml.etree.ElementTree as ElementTree

use cElementTree instead of ElementTree, it's faster but doesn't allow
subclassing. You could also using a try/except to support lxml, which
are bindings for libxml2 and are very fast.

> +from collections import namedtuple, OrderedDict

One of the biggest problems with this series is that OrderedDict is a
2.7 feature, and it's been made clear that 2.6 is a hard requirement for
part of our community. I don't personally care that much, but I suspect
you'll get push back from others.

> +from textwrap import dedent

You're not using dedent or namedtuple



> +
> +# Export 'debug' so other Piglit modules can easily enable it.
> +debug = False

Constants should be all caps

> +
> +def _log_debug(msg):
> +    if debug:
> +        sys.stderr.write('debug: {0}: {1}\n'.format(__name__, msg))

Could you use the print function from __future__? If you do then you can
just use:
print('my message', file=sys.stderr)

It'll give you the newline for free, and it also prevents flushing
problems.

All top level functions and classes get two spaces between them

> +
> +def parse():
> +    """Parse gl.xml and return a Registry object."""
> +    filename = os.path.join(os.path.dirname(__file__), 'gl.xml')
> +    elem = ElementTree.parse(filename)
> +    xml_registry = elem.getroot()
> +    return Registry(xml_registry)
> +
> +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))
> +    """

Have you seen python's doctests? this would be trivial to convert to a
doctest.

> +
> +    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)`. +        """
> +        if isinstance(key, str):
> +            self.__key_func = lambda elem: getattr(elem, key)
> +        else:
> +            self.__key_func = key
> +
> +        self.__dict = OrderedDict()
> +        for i in elems:
> +            self.add(i)
> +
> +    def __or__(x, y):
> +        """Same as `union`."""
> +        return x.union(y)

this seems wrong to me, shouldn't it be:
def __or__(self, y):
	return self.union(y)

> +
> +    def __contains__(self, key):
> +        return key in self.__dict
> +
> +    def __delitem__(self, key):
> +        del self.__dict[key]
> +
> +    def __getitem__(self, key):
> +        return self.__dict[key]
> +
> +    def __iter__(self):
> +        return self.__dict.itervalues()
> +
> +    def __len__(self):
> +        return len(self.__dict)
> +
> +    def __repr__(self):
> +        return '{0}({1})'.format(self.__class__.__name__,
> list(self.keys())) +
> +    def copy(self):
> +        """Return shallow copy."""
> +        other = OrderedKeyedSet(key=self.__key_func)
> +        other.__dict = self.__dict.copy()

you could also import the copy module, it's nice

> +        return other
> +
> +    def add(self, x):
> +        self.__dict[self.__key_func(x)] = x
> +
> +    def clear(self):
> +        self.__dict.clear()
> +
> +    def extend(self, elems):
> +        for e in elems:
> +            self.add(e)
> +
> +    def get(self, key, default):
> +        return self.__dict.get(key, default)
> +
> +    def get_key(self, x):
> +        return self.__key_func(x)
> +
> +    def items(self, x):
> +        return self.__dict.iteritems()
> +
> +    def keys(self):
> +        return self.__dict.iterkeys()
> +
> +    def values(self):
> +        return self.__dict.itervalues()
> +
> +    def pop(self, key):
> +        return self.__dict.pop(key)
> +
> +    def sort_by_key(self):
> +        self.__dict = OrderedDict(sorted(self.items(), key=lambda i: i[0]))
> +
> +    def sort_by_value(self):
> +        self.__dict = OrderedDict(sorted(self.items(), key=lambda i: i[1]))
> +
> +    def union(self, other):
> +        """Return the union of two sets as a new set.
> +
> +        The the new set is ordered so that all elements of self precede
> those +        of other, and the order of elements within each origin set
> is +        preserved.
> +
> +        The new set's key function is taken from self.  On key collisions,
> set +        y has precedence over x.
> +        """
> +        u = self.copy()
> +        u.extend(other)
> +        return u
> +
> +class Registry(object):
> +    """The toplevel <registry> element.
> +
> +    Attributes:
> +        features: An OrderedKeyedSet of class Feature, where each element
> +        represents a <feature> element.
> +
> +        extensions: An OrderedKeyedSet of class Extension, where each
> element +        represents a <extension> element.
> +
> +        commands: An OrderedKeyedSet of class Command, where each element
> +        represents a <command> element.
> +
> +        enum_groups: An ordered collection of class EnumGroup, where each
> +        element represents an <enums> element.
> +
> +        enums: An OrderedKeyedSet of class Enum, where each element
> +        represents an <enum> element.
> +
> +        vendor_namespaces: A collection of all vendor prefixes and 
suffixes.
> +        For example, "ARB", "EXT", "CHROMIUM", "NV".
> +    """
> +
> +    def __init__(self, xml_registry):
> +        """Parse the <registry> element."""
> +
> +        assert(xml_registry.tag == 'registry')
> +
> +        self.features = OrderedKeyedSet(
> +            key='name', elems=(
> +                Feature(xml_feature)
> +                for xml_feature in xml_registry.iterfind('./feature')
> +            )
> +        )
> +
> +        self.extensions = OrderedKeyedSet(
> +            key='name', elems=(
> +                Extension(xml_extension)
> +                for xml_extension in
> +                xml_registry.iterfind('./extensions/extension')
> +            )
> +        )
> +
> +        self.commands = OrderedKeyedSet(
> +            key='name', elems=(
> +                Command(xml_command)
> +                for xml_command in
> +                xml_registry.iterfind('./commands/command')
> +            )
> +        )
> +
> +        self.enum_groups = [
> +            EnumGroup(xml_enums)
> +            for xml_enums in xml_registry.iterfind('./enums')
> +        ]
> +
> +        self.__fix_enum_groups()
> +
> +        self.enums = OrderedKeyedSet(
> +            key='name', elems=(
> +                enum
> +                for group in self.enum_groups
> +                for enum in group.enums
> +                if not enum.is_collider
> +            )
> +        )
> +

I'm still not crazy about these 3+ line comprehensions.

> +        self.vendor_namespaces = {
> +            extension.vendor_namespace
> +            for extension in self.extensions
> +            if extension.vendor_namespace is not None
> +        }
> +
> +        for feature in self.features:
> +            feature._link_commands(self.commands)
> +            feature._link_enums(self.enums)
> +
> +        for extension in self.extensions:
> +            extension._link_commands(self.commands)
> +            extension._link_enums(self.enums)
> +
> +        self.__set_command_name_parts()
> +
> +    def __fix_enum_groups(self):
> +        for enum_group in self.enum_groups:
> +            if (enum_group.name is None
> +                and enum_group.vendor == 'SGI'
> +                and enum_group.start == '0x8000'
> +                and enum_group.end == '0x80BF'):
> +                   self.enum_groups.remove(enum_group)
> +            elif (enum_group.name is None
> +                  and enum_group.vendor == 'ARB'
> +                  and enum_group.start == None
> +                  and enum_group.end == None):
> +                     enum_group.start = '0x8000'
> +                     enum_group.start = '0x80BF'
> +
> +    def __set_command_name_parts(self):
> +        suffix_choices = '|'.join(self.vendor_namespaces)
> +        regex = '^(?P<basename>[a-zA-Z0-9_]+?)
(?P<vendor_suffix>{suffix_choices})?$'
> +        regex = regex.format(suffix_choices=suffix_choices)
> +        _log_debug('setting command name parts with regex 
{0!r}'.format(regex))
> +        regex = re.compile(regex)
> +        for command in self.commands:
> +            command._set_name_parts(regex)
> +
> +class Feature(object):
> +    """A <feature> 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)
> +
> +        commands: An OrderedKeyedSet of class Command that contains all
> +            <command> subelements.
> +
> +        enums: An OrderedKeyedSet of class Enum that contains all <enum>
> +            subelements.
> +    """
> +
> +    def __init__(self, xml_feature):
> +        """Parse a <feature> element."""
> +
> +        # Example <feature> element:
> +        #
> +        #    <feature api="gles2" name="GL_ES_VERSION_3_1" number="3.1">
> +        #        <!-- arrays_of_arrays features -->
> +        #        <require/>
> +        #        <!-- compute_shader features -->
> +        #        <require>
> +        #            <command name="glDispatchCompute"/>
> +        #            <command name="glDispatchComputeIndirect"/>
> +        #            <enum name="GL_COMPUTE_SHADER"/>
> +        #            <enum name="GL_MAX_COMPUTE_UNIFORM_BLOCKS"/>
> +        #            ...
> +        #        </require>
> +        #        <!-- draw_indirect features -->
> +        #        <require>
> +        #            <command name="glDrawArraysIndirect"/>
> +        #            <command name="glDrawElementsIndirect"/>
> +        #            <enum name="GL_DRAW_INDIRECT_BUFFER"/>
> +        #            <enum name="GL_DRAW_INDIRECT_BUFFER_BINDING"/>
> +        #        </require>
> +        #        ...
> +        #    </feature>

This could be part of the docstring

> +
> +        assert(xml_feature.tag == 'feature')
> +        self._xml_feature = xml_feature
> +
> +        # Parse the <feature> tag's attributes.
> +        self.api = xml_feature.get('api')
> +        self.name = xml_feature.get('name')
> +
> +        self.version_str = xml_feature.get('number')
> +        self.version_float = float(self.version_str)
> +        self.version_int = int(10 * self.version_float)
> +
> +        self.commands = OrderedKeyedSet(key='name')
> +        self.enums = OrderedKeyedSet(key='name')
> +
> +    def _link_commands(self, commands):
> +        """Parse <command> subelements and link them to the Command objects
> in +        'commands'.
> +        """
> +        for xml_command in self._xml_feature.iterfind('./require/command'):
> +            command_name = xml_command.get('name')
> +            command = commands[command_name]
> +            assert(isinstance(command, Command))
> +            _log_debug('link command {0!r} and feature
> {1!r})'.format(command.name, self.name)) +           
> self.commands.add(command)
> +            command.features.add(self)
> +
> +    def _link_enums(self, enums):
> +        """Parse <enum> subelements and link them to the Command objects in
> +        'enums'.
> +        """
> +        for xml_enum in self._xml_feature.iterfind('./require/enum'):
> +            enum_name = xml_enum.get('name')
> +            enum = enums[enum_name]
> +            assert(isinstance(enum, Enum))
> +            _log_debug('link command {0!r} and feature
> {1!r}'.format(enum.name, self.name)) +            self.enums.add(enum)
> +            enum.features.add(self)
> +
> +class Extension(object):
> +    """An <extension> XML element.
> +
> +    Attributes:
> +        name: The XML element's 'name' attribute.
> +
> +        supported_apis: The collection of api strings in the XML element's
> +            'supported' attribute. For example, ['gl', 'glcore'].
> +
> +        vendor_namespace: For example, "AMD". May be None.
> +
> +        commands: An OrderedKeyedSet of class Command that contains all
> +            <command> subelements.
> +
> +        enums: An OrderedKeyedSet of class Enum that contains all <enum>
> +            subelements.
> +    """
> +
> +    __vendor_regex = re.compile(r'^GL_(?P<vendor_namespace>[A-Z]+)_')
> +
> +    def __init__(self, xml_extension):
> +        """Parse an <extension> element."""
> +
> +        # Example <extension> element:
> +        #     <extension name="GL_ARB_ES2_compatibility"
> supported="gl|glcore"> +        #         <require>
> +        #             <enum name="GL_FIXED"/>
> +        #             <enum name="GL_IMPLEMENTATION_COLOR_READ_TYPE"/>
> +        #             ...
> +        #             <command name="glReleaseShaderCompiler"/>
> +        #             <command name="glShaderBinary"/>
> +        #             ...
> +        #         </require>
> +        #     </extension>
> +
> +        assert(xml_extension.tag == 'extension')
> +        self._xml_extension = xml_extension
> +
> +        # Parse the <extension> tag's attributes.
> +        self.name = xml_extension.get('name')
> +        self.supported_apis = xml_extension.get('supported').split('|')
> +
> +        self.vendor_namespace = None
> +        match = Extension.__vendor_regex.match(self.name)
> +        if match is not None:
> +            self.vendor_namespace =
> match.groupdict().get('vendor_namespace', None) +
> +        self.commands = OrderedKeyedSet(key='name')
> +        self.enums = OrderedKeyedSet(key='name')
> +
> +    def __cmp__(x, y):
> +        """Compare by name."""
> +        return cmp(x.name, y.name)
> +
> +    def _link_commands(self, commands):
> +        """Parse <command> subelements and link them to the Command objects
> in +        'commands'.
> +        """
> +        for xml_command in
> self._xml_extension.iterfind('./require/command'): +           
> command_name = xml_command.get('name')
> +            command = commands[command_name]
> +            assert(isinstance(command, Command))
> +            _log_debug('link command {0!r} and extension
> {1!r}'.format(command.name, self.name)) +           
> self.commands.add(command)
> +            command.extensions.add(self)
> +
> +    def _link_enums(self, enums):
> +        """Parse <enum> subelements and link them to the Command objects in
> +        'enums'.
> +        """
> +        for xml_enum in self._xml_extension.iterfind('./require/enum'):
> +            enum_name = xml_enum.get('name')
> +            enum = enums[enum_name]
> +            assert(isinstance(enum, Enum))
> +            _log_debug('link command {0!r} and extension
> {1!r}'.format(enum.name, self.name)) +            self.enums.add(enum)
> +            enum.extensions.add(self)
> +
> +class CommandParam(object):
> +    """A <param> 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 <param> element."""
> +
> +        # Example <param> elements:
> +        #
> +        #    <param>const <ptype>GLchar</ptype> *<name>name</name></param>
> +        #    <param len="1"><ptype>GLsizei</ptype>
> *<name>length</name></param> +        #    <param
> len="bufSize"><ptype>GLint</ptype> *<name>values</name></param> +        # 
>   <param><ptype>GLenum</ptype> <name>shadertype</name></param> +        #  
>  <param group="sync"><ptype>GLsync</ptype> <name>sync</name></param> +
> +        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 <name> 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 <command> XML element.
> +
> +    Attributes:
> +        name: The XML element's 'name' attribute.
> +
> +        features: An OrderedKeyedSet of class Feature that enumerates all
> +            features that require this command.
> +
> +        extensions: An OrderedKeyedSet of class Extensions that enumerates
> all +            extensions that require this command.
> +
> +        c_return_type: For example, "void *".
> +
> +        alias: The XML element's 'alias' element. May be None.
> +
> +        param_list: List of class CommandParam that contains all <param>
> +            subelements.
> +    """
> +
> +    def __init__(self, xml_command):
> +        """Parse a <command> element."""
> +
> +        # Example <command> element:
> +        #
> +        #    <command>
> +        #        <proto>void <name>glTexSubImage2D</name></proto>
> +        #        <param group="TextureTarget"><ptype>GLenum</ptype>
> <name>target</name></param> +        #        <param
> group="CheckedInt32"><ptype>GLint</ptype> <name>level</name></param> +     
>   #        <param group="CheckedInt32"><ptype>GLint</ptype>
> <name>xoffset</name></param> +        #        <param
> group="CheckedInt32"><ptype>GLint</ptype> <name>yoffset</name></param> +   
>     #        <param><ptype>GLsizei</ptype> <name>width</name></param> +    
>    #        <param><ptype>GLsizei</ptype> <name>height</name></param> +    
>    #        <param group="PixelFormat"><ptype>GLenum</ptype>
> <name>format</name></param> +        #        <param
> group="PixelType"><ptype>GLenum</ptype> <name>type</name></param> +       
> #        <param len="COMPSIZE(format,type,width,height)">const void
> *<name>pixels</name></param> +        #        <glx type="render"
> opcode="4100"/>
> +        #        <glx type="render" opcode="332" name="glTexSubImage2DPBO"
> comment="PBO protocol"/> +        #    </command>
> +        #
> +
> +        assert(xml_command.tag == 'command')
> +        self.__xml_command = xml_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.features = OrderedKeyedSet(key='name')
> +        self.extensions = OrderedKeyedSet(key='name')
> +
> +        # Parse the return type from the <proto> element.
> +        #
> +        # Example of a difficult <proto> element:
> +        #     <proto group="String">const <ptype>GLubyte</ptype>
> *<name>glGetStringi</name></proto> +        c_return_type_text =
> list(xml_proto.itertext())
> +        c_return_type_text.pop(-1) # Pop off the text from the <name>
> 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 xml_command.iterfind('./param')
> +        ]
> +
> +        _log_debug(('parsed {self.__class__.__name__}('
> +                     'name={self.name!r}, '
> +                     'alias={self.alias!r}, '
> +                     'prototype={self.c_prototype!r}, '
> +                     'features={self.features}, '
> +                     'extensions={self.extensions})').format(self=self))
> +
> +    def __cmp__(x, y):
> +        return cmp(x.name, y.name)

Shouldn't this be:
def __cmp__(self, other):
	return cmp(self.name, other.name)

> +
> +    def __repr__(self):
> +        cls = self.__class__
> +        return '{cls.__name__}({self.name!r})'.format(**locals())
> +
> +    @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
> +        ])

Drop the [] and make this a generator instead of a comprehension. It
will save memory

> +
> +    @property
> +    def c_unnamed_param_list(self):
> +        """For example, "GLenum, GLfloat" for glAccum."""
> +        return ', '.join([
> +            param.c_type
> +            for param in self.param_list
> +        ])

and on the rest of them

> +
> +    @property
> +    def c_untyped_param_list(self):
> +        """For example, "op, value" for glAccum."""
> +        return ', '.join([
> +            param.name
> +            for param in self.param_list
> +        ])
> +
> +    @property
> +    def requirements(self):
> +        """The union of features and extensions that require this
> commnand.""" +        return self.features | self.extensions
> +
> +    def _set_name_parts(self, regex):
> +        groups = regex.match(self.name).groupdict()
> +        self.basename = groups['basename']
> +        self.vendor_suffix = groups.get('vendor_suffix', None)
> +
> +class EnumGroup(object):
> +    """An <enums> 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. May be
> +            None.
> +
> +        enums: An OrderedKeyedSet of class Enum that contains all <enum>
> +            sublements.
> +    """
> +
> +    # 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 <enums type="bitmask">.  However, in
> gl.xml each block of +    # non-bitmask enums is introduced by a comment
> that describes the block's +    # "type", even if the <enums> 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 <enums type=>. +    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',
> +    )
> +
> +    @staticmethod
> +    def __fix_xml(xml_elem):
> +        """Add missing attributes and fix misnamed ones."""
> +        if xml_elem.get('namespace') == 'OcclusionQueryEventMaskAMD':
> +            # This tag's attributes are totally broken.
> +            xml_elem.set('namespace', 'GL')
> +            xml_elem.set('group', 'OcclusionQueryEventMaskAMD')
> +            xml_elem.set('type', 'bitmask')
> +        elif xml_elem.get('vendor', None) == 'ARB':
> +            enums = xml_elem.findall('./enum')
> +            if (len(enums) != 0
> +                    and enums[0].get('value') == '0x8000'
> +                    and enums[-1].get('value') == '0x80BF'):
> +                # This tag lacks 'start' and 'end' attributes.
> +                xml_elem.set('start','0x8000')
> +                xml_elem.set('end', '0x80BF')
> +
> +    def __init__(self, xml_enums):
> +        """Parse an <enums> element."""
> +
> +        # Example of a bitmask group:
> +        #
> +        #     <enums namespace="GL" group="SyncObjectMask" type="bitmask">
> +        #         <enum value="0x00000001"
> name="GL_SYNC_FLUSH_COMMANDS_BIT"/> +        #         <enum
> value="0x00000001" name="GL_SYNC_FLUSH_COMMANDS_BIT_APPLE"/> +        #    
> </enums>
> +        #
> +        # Example of a group that resides in OpenGL's default enum
> namespace: +        #
> +        #     <enums namespace="GL" start="0x0000" end="0x7FFF"
> vendor="ARB" comment="..."> +        #         <enum value="0x0000"
> name="GL_POINTS"/>
> +        #         <enum value="0x0001" name="GL_LINES"/>
> +        #         <enum value="0x0002" name="GL_LINE_LOOP"/>
> +        #         ...
> +        #     </enums>
> +        #
> +        # Example of a non-bitmask group that resides outside OpenGL's
> default +        # enum namespace:
> +        #
> +        #     <enums namespace="GL" group="PathRenderingTokenNV"
> vendor="NV"> +        #         <enum value="0x00"
> name="GL_CLOSE_PATH_NV"/>
> +        #         <enum value="0x02" name="GL_MOVE_TO_NV"/>
> +        #         <enum value="0x03" name="GL_RELATIVE_MOVE_TO_NV"/>
> +        #         ...
> +        #     </enums>
> +        #
> +
> +        EnumGroup.__fix_xml(xml_enums)
> +
> +        self.name = xml_enums.get('group', None)
> +        _log_debug(('start parsing {self.__class__.__name__}('
> +                    'name={self.name!r}').format(self=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)
> +
> +        # Parse the <enum> subelements.
> +        _log_debug('start parsing subelements of {self}'.format(self=self))
> +        self.enums = OrderedKeyedSet(
> +            key='name', elems=(
> +                Enum(self, xml_enum)
> +                for xml_enum in xml_enums.iterfind('./enum')
> +            )
> +        )
> +        _log_debug('parsed {0}'.format(self))
> +
> +    def __repr__(self):
> +        return (
> +            '{self.__class__.__name__}('
> +            'name={self.name!r}, '
> +            'type={self.type!r}, '
> +            'start={self.start}, '
> +            'end={self.end}, '
> +            'enums={enums})'
> +        ).format(
> +            self=self,
> +            enums= ', '.join([repr(e) for e in self.enums]),
> +        )
> +
> +    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'
> +
> +    def __post_fix_xml(self):
> +        if (self.vendor == "ARB"
> +                and self.start is None
> +                and self.end is None
> +                and self.enums[0].num_value == 0x8000
> +                and self.enums[-1].num_value == 0x80BF):
> +            self.start = '0x8000'
> +            self.end = '0x0BF'
> +
> +class Enum(object):
> +    """An <enum> 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.
> +
> +        features: An OrderedKeyedSet of class Feature that enumerates all
> +            features that require this enum.
> +
> +        extensions: An OrderedKeyedSet of class Extensions that enumerates
> all +            extensions that require this enum.
> +    """
> +
> +    def __init__(self, enum_group, xml_enum):
> +        """Parse an <enum> tag located at path registry/enums/enum."""
> +
> +        # Example <enum> element:
> +        #     <enum value="0x0000" name="GL_POINTS"/>
> +
> +        assert(isinstance(enum_group, EnumGroup))
> +        assert(xml_enum.tag == 'enum')
> +
> +        self.group = enum_group
> +        self.name = xml_enum.get('name')
> +        self.api = xml_enum.get('api')
> +        self.str_value = xml_enum.get('value')
> +        self.features = OrderedKeyedSet(key='name')
> +        self.extensions = OrderedKeyedSet(key='name')
> +        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):
> +        return ('{self.__class__.__name__}('
> +                'name={self.name!r}, '
> +                'value={self.str_value!r}, '
> +                'features={self.features}, '
> +                'extensions={self.extensions})').format(self=self)
> +
> +    @property
> +    def is_collider(self):
> +        """Does the enum's name collide with another enum's name?"""
> +        if self.name == 'GL_ACTIVE_PROGRAM_EXT' and self.api == 'gles2':
> +            # This enum has different numerical values in GL (0x8B8D) and
> +            # in GLES (0x8259).
> +            return True
> +        else:
> +            return False
> +
> +    @property
> +    def requirements(self):
> +        return self.features | self.extensions
> diff --git a/tests/util/gen_dispatch.py b/tests/util/gen_dispatch.py
> index c97af94..d445932 100644
> --- a/tests/util/gen_dispatch.py
> +++ b/tests/util/gen_dispatch.py
> @@ -1,4 +1,4 @@
> -# Copyright 2012 Intel Corporation
> +# 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"), @@ -19,664 +19,290 @@
>  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
> DEALINGS # IN THE SOFTWARE.
> 
> -# This script generates a C file (and corresponding header) allowing
> -# Piglit to dispatch calls to OpenGL based on a JSON description of
> -# the GL API (and extensions).
> -#
> -# Invoke this script with 3 command line arguments: the JSON input
> -# filename, the C output filename, and the header outpit filename.
> -#
> -#
> -# The input looks like this:
> -#
> -# {
> -#   "categories": {
> -#     <category name>: {
> -#       "kind": <"GL" or "GLES" for a GL spec API, "extension" for an
> extension>, -#       "gl_10x_version": <For a GL version, version number
> times 10>, -#       "extension_name" <For an extension, name of the
> extension> -#     }, ...
> -#   },
> -#   "enums": {
> -#     <enum name, without "GL_" prefix>: {
> -#       "value_int": <value integer>
> -#       "value_str": <value string>
> -#     }, ...
> -#   },
> -#   "functions": {
> -#     <function name, without "gl" prefix>: {
> -#       "categories": <list of categories in which this function appears>,
> -#       "param_names": <list of param names>,
> -#       "param_types": <list of param types>,
> -#       "return_type": <type, or "void" if no return>
> -#     }, ...
> -#   },
> -#   "function_alias_sets": {
> -#     <list of synonymous function names>, ...
> -#   },
> -# }
> -#
> -#
> -# The generated header consists of the following:
> -#
> -# - A typedef for each function, of the form that would normally
> -#   appear in gl.h or glext.h, e.g.:
> -#
> -#   typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERPROC)(GLenum, GLenum);
> -#   typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERARBPROC)(GLenum, GLenum);
> -#
> -# - A set of extern declarations for "dispatch function pointers".
> -#   There is one dispatch function pointer for each set of synonymous
> -#   functions in the GL API, e.g.:
> -#
> -#   extern PFNGLMAPBUFFERPROC piglit_dispatch_glMapBuffer;
> -#
> -# - A set of #defines mapping each function name to the corresponding
> -#   dispatch function pointer, e.g.:
> -#
> -#   #define glMapBuffer piglit_dispatch_glMapBuffer
> -#   #define glMapBufferARB piglit_dispatch_glMapBuffer
> -#
> -# - A #define for each enum in the GL API, e.g.:
> -#
> -#   #define GL_FRONT 0x0404
> -#
> -# - A #define for each extension, e.g.:
> -#
> -#   #define GL_ARB_vertex_buffer_object 1
> -#
> -# - A #define for each known GL version, e.g.:
> -#
> -#   #define GL_VERSION_1_5 1
> -#
> -#
> -# The generated C file consists of the following:
> -#
> -# - A resolve function corresponding to each set of synonymous
> -#   functions in the GL API.  The resolve function determines which of
> -#   the synonymous names the implementation supports (by consulting
> -#   the current GL version and/or the extension string), and calls
> -#   either get_core_proc() or get_ext_proc() to get the function
> -#   pointer.  It stores the result in the dispatch function pointer,
> -#   and then returns it as a generic void(void) function pointer.  If
> -#   the implementation does not support any of the synonymous names,
> -#   it calls unsupported().  E.g.:
> -#
> -#   /* glMapBuffer (GL 1.5) */
> -#   /* glMapbufferARB (GL_ARB_vertex_buffer_object) */
> -#   static piglit_dispatch_function_ptr resolve_glMapBuffer()
> -#   {
> -#     if (check_version(15))
> -#       piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERPROC)
> get_core_proc("glMapBuffer", 15); -#     else if
> (check_extension("GL_ARB_vertex_buffer_object"))
> -#       piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERARBPROC)
> get_ext_proc("glMapBufferARB"); -#     else
> -#       unsupported("MapBuffer");
> -#     return (piglit_dispatch_function_ptr) piglit_dispatch_glMapBuffer;
> -#   }
> -#
> -# - A stub function corresponding to each set of synonymous functions
> -#   in the GL API.  The stub function first calls
> -#   check_initialized().  Then it calls the resolve function to
> -#   ensure that the dispatch function pointer is set.  Finally, it
> -#   dispatches to the GL function through the dispatch function
> -#   pointer.  E.g.:
> -#
> -#   static GLvoid * APIENTRY stub_glMapBuffer(GLenum target, GLenum access)
> -#   {
> -#     check_initialized();
> -#     resolve_glMapBuffer();
> -#     return piglit_dispatch_glMapBuffer(target, access);
> -#   }
> -#
> -# - A declaration for each dispatch function pointer, e.g.:
> -#
> -#   PFNGLMAPBUFFERPROC piglit_dispatch_glMapBuffer = stub_glMapBuffer;
> -#
> -# - An function, reset_dispatch_pointers(), which resets each dispatch
> -#   pointer to the corresponding stub function.
> -#
> -# - A table function_names, containing the name of each function in
> -#   alphabetical order (including the "gl" prefix).
> -#
> -# - A table function_resolvers, containing a pointer to the resolve
> -#   function corresponding to each entry in function_names.
> 
> -import collections
> +"""
> +Generate C source code from Khronos XML.
> +"""
> +
> +import argparse
> +import mako
>  import os.path
>  import sys
> -try:
> -    import simplejson as json
> -except:
> -    import json
> -
> -
> -# Generate a top-of-file comment cautioning that the file is
> -# auto-generated and should not be manually edited.
> -def generated_boilerplate():
> -    return """\
> -/**
> - * This file was automatically generated by the script {0!r}.
> - *
> - * DO NOT EDIT!
> - *
> - * To regenerate, run "make piglit_dispatch_gen" from the toplevel
> directory. - *
> - * Copyright 2012 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.
> - */
> -""".format(os.path.basename(sys.argv[0]))
> -
> -
> -# Certain param names used in OpenGL are reserved by some compilers.
> -# Rename them.
> -PARAM_NAME_FIXUPS = {'near': 'hither', 'far': 'yon'}
> -
> -
> -def fixup_param_name(name):
> -    if name in PARAM_NAME_FIXUPS:
> -        return PARAM_NAME_FIXUPS[name]
> -    else:
> -        return name
> -
> -
> -# Internal representation of a category.
> -#
> -# - For a category representing a GL version, Category.kind is 'GL'
> -#   and Category.gl_10x_version is 10 times the GL version (e.g. 21
> -#   for OpenGL version 2.1).
> -#
> -# - For a category representing an extension, Category.kind is
> -#   'extension' and Category.extension_name is the extension name
> -#   (including the 'GL_' prefix).
> -class Category(object):
> -    def __init__(self, json_data):
> -        self.kind = json_data['kind']
> -        if 'gl_10x_version' in json_data:
> -            self.gl_10x_version = json_data['gl_10x_version']
> -        if 'extension_name' in json_data:
> -            self.extension_name = json_data['extension_name']
> -
> -    # Generate a human-readable representation of the category (for
> -    # use in generated comments).
> -    def __str__(self):
> -        if self.kind == 'GL':
> -            return 'GL {0}.{1}'.format(
> -                self.gl_10x_version // 10, self.gl_10x_version % 10)
> -        if self.kind == 'GLES':
> -            return 'GLES {0}.{1}'.format(
> -                self.gl_10x_version // 10, self.gl_10x_version % 10)
> -        elif self.kind == 'extension':
> -            return self.extension_name
> -        else:
> -            raise Exception(
> -                'Unexpected category kind {0!r}'.format(self.kind))
> -
> -
> -# Internal representation of a GL function.
> -#
> -# - Function.name is the name of the function, without the 'gl'
> -#   prefix.
> -#
> -# - Function.param_names is a list containing the name of each
> -#   function parameter.
> -#
> -# - Function.param_types is a list containing the type of each
> -#   function parameter.
> -#
> -# - Function.return_type is the return type of the function, or 'void'
> -#   if the function has no return.
> -#
> -# - Function.category is a Category object describing the extension or
> -#   GL version the function is defined in.
> -class Function(object):
> -    def __init__(self, name, json_data):
> -        self.name = name
> -        self.param_names = [
> -            fixup_param_name(x) for x in json_data['param_names']]
> -        self.param_types = json_data['param_types']
> -        self.return_type = json_data['return_type']
> -        self.categories = json_data['categories']
> -
> -    # Name of the function, with the 'gl' prefix.
> -    @property
> -    def gl_name(self):
> -        return 'gl' + self.name
> 
> -    # Name of the function signature typedef corresponding to this
> -    # function.  E.g. for the glGetString function, this is
> -    # 'PFNGLGETSTRINGPROC'.
> -    @property
> -    def typedef_name(self):
> -        return 'pfn{0}proc'.format(self.gl_name).upper()
> -
> -    # Generate a string representing the function signature in C.
> -    #
> -    # - name is inserted before the opening paren--use '' to generate
> -    #   an anonymous function type signature.
> -    #
> -    # - If anonymous_args is True, then the signature contains only
> -    #   the types of the arguments, not the names.
> -    def c_form(self, name, anonymous_args):
> -        if self.param_types:
> -            if anonymous_args:
> -                param_decls = ', '.join(self.param_types)
> +from cStringIO import StringIO

unused

> +from collections import namedtuple
> +from textwrap import dedent
> +from mako.template import Template
> +
> +PIGLIT_TOP_DIR = os.path.join(os.path.dirname(__file__), '..', '..')
> +sys.path.append(PIGLIT_TOP_DIR)
> +import registry.gl
> +from registry.gl import OrderedKeyedSet
> +
> +PROG_NAME = os.path.basename(sys.argv[0])
> +debug = False
> +
> +def main():
> +    global debug
> +
> +    argparser = argparse.ArgumentParser(prog=PROG_NAME)

you can drop the prog assignment, you've assigned it to the default
value

> +    argparser.add_argument('-o', '--out-dir')

add required=True to this argument and drop the is None check

> +    argparser.add_argument('-d', '--debug', action='store_true',
> default=False) +    args = argparser.parse_args()
> +
> +    debug = args.debug
> +    if args.out_dir is None:
> +        argparser.error('missing OUT_DIR')

see previous comment

> +
> +    registry.gl.debug = debug
> +    gl_registry = registry.gl.parse()
> +
> +    dispatch_h = open(os.path.join(args.out_dir, 'generated_dispatch.h'),
> 'w') +    dispatch_c = open(os.path.join(args.out_dir,
> 'generated_dispatch.c'), 'w') +   
> DispatchCode(gl_registry).emit(dispatch_h, dispatch_c)
> +    dispatch_h.close()
> +    dispatch_c.close()
> +
> +def log_debug(msg):
> +    if debug:
> +        sys.stderr.write('debug: {0}: {1}\n'.format(PROG_NAME, msg))
> +
> +copyright_block = dedent('''\
> +    /**
> +     * DO NOT EDIT!
> +     * This file was generated by the script {PROG_NAME!r}.
> +     *
> +     * 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.
> +     */'''
> +).format(PROG_NAME=PROG_NAME)
> +
> +Api = namedtuple('Api', ('name', 'base_version_int', 'c_piglit_token'))
> +"""Api corresponds to the XML <feature> tag's 'api' attribute."""
> +
> +apis = OrderedKeyedSet(
> +    key='name', elems=(
> +        Api(name='gl',      c_piglit_token='PIGLIT_DISPATCH_GL', 
> base_version_int=10), +        Api(name='glcore', 
> c_piglit_token='PIGLIT_DISPATCH_GL',  base_version_int=10), +       
> Api(name='gles1',   c_piglit_token='PIGLIT_DISPATCH_ES1',
> base_version_int=11), +        Api(name='gles2',  
> c_piglit_token='PIGLIT_DISPATCH_ES1', base_version_int=20), +    )
> +)
> +
> +class CommandAliasMap(OrderedKeyedSet):
> +
> +    unaliasable_commands = {
> +        'glGetObjectParameterivAPPLE',
> +        'glGetObjectParameterivARB',
> +    }

using {} for sets is a 2.7 feature, beyond that I haven't seen it used
much, which I assume is because {} is ubiquitous for dict(). We should
probably just use explicit set(). This comment applies to the whole file

> +
> +    def __init__(self, commands=()):
> +        OrderedKeyedSet.__init__(self, key='name', elems=commands)
> +
> +    def add(self, alias_set):
> +        assert(isinstance(alias_set, CommandAliasSet))
> +        OrderedKeyedSet.add(self, alias_set)
> +
> +    def add_commands(self, commands):
> +        for command in commands:
> +            assert(isinstance(command, registry.gl.Command))
> +
> +            if command.name in CommandAliasMap.unaliasable_commands:
> +                alias_name = command.name
>              else:
> -                param_decls = ', '.join(
> -                    '{0} {1}'.format(*p)
> -                    for p in zip(self.param_types, self.param_names))
> -        else:
> -            param_decls = 'void'
> -        return '{rettype} {name}({param_decls})'.format(
> -            rettype=self.return_type, name=name,
> -            param_decls=param_decls)
> +                alias_name = command.basename
> 
> +            alias_set = self.get(alias_name, None)
> 
> -# Internal representation of an enum.
> -#
> -# - Enum.value_int is the value of the enum, as a Python integer.
> -#
> -# - Enum.value_str is the value of the enum, as a string suitable for
> -#   emitting as C code.
> -class Enum(object):
> -    def __init__(self, json_data):
> -        self.value_int = json_data['value_int']
> -        self.value_str = json_data['value_str']
> +            if alias_set is None:
> +                self.add(CommandAliasSet(alias_name, [command]))
> +            else:
> +                alias_set.add(command)
> 
> +class CommandAliasSet(OrderedKeyedSet):
> 
> -# Data structure keeping track of a set of synonymous functions.  Such
> -# a set is called a "dispatch set" because it corresponds to a single
> -# dispatch pointer.
> -#
> -# - DispatchSet.cat_fn_pairs is a list of pairs (category, function)
> -#   for each category this function is defined in.  The list is sorted
> -#   by category, with categories of kind 'GL' and then 'GLES' appearing
> first. -class DispatchSet(object):
> -    # Initialize a dispatch set given a list of synonymous function
> -    # names.
> -    #
> -    # - all_functions is a dict mapping all possible function names to
> -    #   the Function object describing them.
> -    #
> -    # - all_categories is a dict mapping all possible category names
> -    #   to the Category object describing them.
> -    def __init__(self, synonym_set, all_functions, all_categories):
> -        self.cat_fn_pairs = []
> -        for function_name in synonym_set:
> -            function = all_functions[function_name]
> -            for category_name in function.categories:
> -                category = all_categories[category_name]
> -                self.cat_fn_pairs.append((category, function))
> -        # Sort by category, with GL categories preceding extensions.
> -        self.cat_fn_pairs.sort(key=self.__sort_key)
> -
> -    # The first Function object in DispatchSet.functions.  This
> -    # "primary" function is used to name the dispatch pointer, the
> -    # stub function, and the resolve function.
> -    @property
> -    def primary_function(self):
> -        return self.cat_fn_pairs[0][1]
> +    Resolution = namedtuple('Resolution', ('c_condition', 'c_get_proc'))
> 
> -    # The name of the dispatch pointer that should be generated for
> -    # this dispatch set.
> -    @property
> -    def dispatch_name(self):
> -        return 'piglit_dispatch_' + self.primary_function.gl_name
> +    def __init__(self, name, commands):
> +        OrderedKeyedSet.__init__(self, key='name')
> +        self.name = name
> +        for command in commands:
> +            self.add(command)
> 
> -    # The name of the stub function that should be generated for this
> -    # dispatch set.
> -    @property
> -    def stub_name(self):
> -        return 'stub_' + self.primary_function.gl_name
> +    def __cmp__(x, y):
> +        return cmp(x.name, y.name)
> 
> -    # The name of the resolve function that should be generated for
> -    # this dispatch set.
> -    @property
> -    def resolve_name(self):
> -        return 'resolve_' + self.primary_function.gl_name
> -
> -    @staticmethod
> -    def __sort_key(cat_fn_pair):
> -        if cat_fn_pair[0].kind == 'GL':
> -            return 0, cat_fn_pair[0].gl_10x_version
> -        elif cat_fn_pair[0].kind == 'GLES':
> -            return 1, cat_fn_pair[0].gl_10x_version
> -        elif cat_fn_pair[0].kind == 'extension':
> -            return 2, cat_fn_pair[0].extension_name
> -        else:
> -            raise Exception(
> -                'Unexpected category kind
> {0!r}'.format(cat_fn_pair[0].kind)) -
> -
> -# Data structure keeping track of all of the known functions and
> -# enums, and synonym relationships that exist between the functions.
> -#
> -# - Api.enums is a dict mapping enum name to an Enum object.
> -#
> -# - Api.functions is a dict mapping function name to a Function object.
> -#
> -# - Api.function_alias_sets is a list of lists, where each constituent
> -#   list is a list of function names that are aliases of each other.
> -#
> -# - Api.categories is a dict mapping category name to a Category
> -#   object.
> -class Api(object):
> -    def __init__(self, json_data):
> -        self.enums = dict(
> -            (key, Enum(value))
> -            for key, value in json_data['enums'].items())
> -        self.functions = dict(
> -            (key, Function(key, value))
> -            for key, value in json_data['functions'].items())
> -        self.function_alias_sets = json_data['function_alias_sets']
> -        self.categories = dict(
> -            (key, Category(value))
> -            for key, value in json_data['categories'].items())
> -
> -    # Generate a list of (name, value) pairs representing all enums in
> -    # the API.  The resulting list is sorted by enum value.
> -    def compute_unique_enums(self):
> -        enums_by_value = [(enum.value_int, (name, enum.value_str))
> -                          for name, enum in self.enums.items()]
> -        enums_by_value.sort()
> -        return [item[1] for item in enums_by_value]
> -
> -    # A list of all of the extension names declared in the API, as
> -    # Python strings, sorted alphabetically.
>      @property
> -    def extensions(self):
> -        return sorted(
> -            [category_name
> -             for category_name, category in self.categories.items()
> -             if category.kind == 'extension'])
> -
> -    # A list of all of the GL versions declared in the API, as
> -    # integers (e.g. 13 represents GL version 1.3).
> +    def primary_command(self):
> +        return sorted(self)[0]
> +
>      @property
> -    def gl_versions(self):
> -        return sorted(
> -            [category.gl_10x_version
> -             for category in self.categories.values()
> -             if category.kind == 'GL'])
> -
> -    # Generate a list of DispatchSet objects representing all sets of
> -    # synonymous functions in the API.  The resulting list is sorted
> -    # by DispatchSet.stub_name.
> -    def compute_dispatch_sets(self):
> -        sets = [DispatchSet(synonym_set, self.functions, self.categories)
> -                for synonym_set in self.function_alias_sets]
> -        sets.sort(key=lambda ds: ds.stub_name)
> -
> -        return sets
> -
> -    # Generate a list of Function objects representing all functions
> -    # in the API.  The resulting list is sorted by function name.
> -    def compute_unique_functions(self):
> -        return [self.functions[key] for key in
> sorted(self.functions.keys())] -
> -
> -# Read the given input file and return an Api object containing the
> -# data in it.
> -def read_api(filename):
> -    with open(filename, 'r') as f:
> -        return Api(json.load(f))
> -
> -
> -# Generate the resolve function for a given DispatchSet.
> -def generate_resolve_function(ds):
> -    f0 = ds.primary_function
> -
> -    # First figure out all the conditions we want to check in order to
> -    # figure out which function to dispatch to, and the code we will
> -    # execute in each case.
> -    condition_code_pairs = []
> -    for category, f in ds.cat_fn_pairs:
> -        if category.kind in ('GL', 'GLES'):
> -            getter = 'get_core_proc("{0}", {1})'.format(
> -                f.gl_name, category.gl_10x_version)
> -
> -            condition = ''
> -            api_base_version = 0
> -            if category.kind == 'GL':
> -                condition = 'dispatch_api == PIGLIT_DISPATCH_GL'
> -                api_base_version = 10
> -            elif category.gl_10x_version >= 20:
> -                condition = 'dispatch_api == PIGLIT_DISPATCH_ES2'
> -                api_base_version = 20
> -            else:
> -                condition = 'dispatch_api == PIGLIT_DISPATCH_ES1'
> -                api_base_version = 11
> -
> -            # Only check the version for functions that aren't part of the
> -            # core for the PIGLIT_DISPATCH api.
> -            if category.gl_10x_version != api_base_version:
> -                condition = condition + ' && check_version({0})'.format(
> -                    category.gl_10x_version)
> -        elif category.kind == 'extension':
> -            getter = 'get_ext_proc("{0}")'.format(f.gl_name)
> -            condition =
> 'check_extension("{0}")'.format(category.extension_name) -        else:
> -            raise Exception(
> -                'Unexpected category type {0!r}'.format(category.kind))
> -
> -        if f.name == 'TexImage3DEXT':
> -            # Special case: glTexImage3DEXT has a slightly different
> -            # type than glTexImage3D (argument 3 is a GLenum rather
> -            # than a GLint).  This is not a problem, since GLenum and
> -            # GLint are treated identically by function calling
> -            # conventions.  So when calling get_proc_address() on
> -            # glTexImage3DEXT, cast the result to PFNGLTEXIMAGE3DPROC
> -            # to avoid a warning.
> -            typedef_name = 'PFNGLTEXIMAGE3DPROC'
> -        else:
> -            typedef_name = f.typedef_name
> -
> -        code = '{0} = ({1}) {2};'.format(
> -            ds.dispatch_name, typedef_name, getter)
> -
> -        condition_code_pairs.append((condition, code))
> -
> -    # XXX: glDraw{Arrays,Elements}InstancedARB are exposed by
> -    # ARB_instanced_arrays in addition to ARB_draw_instanced, but neither
> -    # gl.spec nor gl.json can accomodate an extension with two categories,
> so -    # insert these cases here.
> -        if f.gl_name in ('glDrawArraysInstancedARB',
> -                         'glDrawElementsInstancedARB'):
> -            condition = 'check_extension("GL_ARB_instanced_arrays")'
> -            condition_code_pairs.append((condition, code))
> -
> -    # Finally, if none of the previous conditions were satisfied, then
> -    # the given dispatch set is not supported by the implementation,
> -    # so we want to call the unsupported() function.
> -    condition_code_pairs.append(
> -        ('true', 'unsupported("{0}");'.format(f0.name)))
> -
> -    # Start the resolve function
> -    resolve_fn = 'static piglit_dispatch_function_ptr {0}()\n'.format(
> -        ds.resolve_name)
> -    resolve_fn += '{\n'
> -
> -    # Output code that checks each condition in turn and executes the
> -    # appropriate case.  To make the generated code more palatable
> -    # (and to avoid compiler warnings), we convert "if (true) FOO;" to
> -    # "FOO;" and "else if (true) FOO;" to "else FOO;".
> -    if condition_code_pairs[0][0] == 'true':
> -        resolve_fn += '\t{0}\n'.format(condition_code_pairs[0][1])
> -    else:
> -        resolve_fn += '\tif
> ({0})\n\t\t{1}\n'.format(*condition_code_pairs[0]) -        for i in
> xrange(1, len(condition_code_pairs)):
> -            if condition_code_pairs[i][0] == 'true':
> -                resolve_fn += '\telse\n\t\t{0}\n'.format(
> -                    condition_code_pairs[i][1])
> -                break
> +    def resolutions(self):
> +        """Iterable of Resolution actions to obtain the GL proc address."""
> +        Resolution = self.__class__.Resolution
> +        for command in self:
> +            for feature in command.features:
> +                api = apis[feature.api]
> +                format_map = dict(command=command, feature=feature,
> api=api) +                condition = 'dispatch_api ==
> {api.c_piglit_token}'.format(**format_map) +                get_proc =
> 'get_core_proc("{command.name}",
> {feature.version_int})'.format(**format_map) +                if
> feature.version_int > api.base_version_int:
> +                    condition += ' &&
> check_version({feature.version_int})'.format(**format_map) +               
> yield Resolution(condition, get_proc)
> +
> +            for extension in command.extensions:
> +                format_map = dict(command=command, extension=extension)
> +                condition =
> 'check_extension("{extension.name}")'.format(**format_map) +               
> get_proc = 'get_ext_proc("{command.name}")'.format(**format_map) +         
>       yield Resolution(condition, get_proc)
> +
> +    def add(self, command):
> +        assert(isinstance(command, registry.gl.Command))
> +        OrderedKeyedSet.add(self, command)
> +
> +class DispatchCode(object):
> +
> +    h_template = Template(dedent('''\
> +        ${copyright}
> +
> +        #ifndef __PIGLIT_DISPATCH_GEN_H__
> +        #define __PIGLIT_DISPATCH_GEN_H__
> +
> +        % for f in sorted(gl_registry.commands):
> +        typedef ${f.c_return_type} (APIENTRY
> *PFN${f.name.upper()}PROC)(${f.c_named_param_list}); +        % endfor
> +        % for alias_set in alias_map:
> +        <% f0 = alias_set.primary_command %>
> +        %   for f in alias_set:
> +        <%      cat_list = ' '.join(['(' + cat.name + ')' for cat in
> f.requirements]) %>\\ +        /* ${f.name} ${cat_list} */
> +        %   endfor
> +        extern PFN${f0.name.upper()}PROC piglit_dispatch_${f0.name};
> +        %   for f in alias_set:
> +        #define ${f.name} piglit_dispatch_${f0.name}
> +        %   endfor
> +        % endfor
> +        % for enum_group in gl_registry.enum_groups:
> +        %   if len(enum_group.enums) > 0:
> +
> +        /* Enum Group ${enum_group.name} */
> +        %     for enum in enum_group.enums:
> +        %         if not enum.is_collider:
> +        #define ${enum.name} ${enum.c_num_literal}
> +        %         endif
> +        %      endfor
> +        %   endif
> +        % endfor
> +
> +        % for extension in gl_registry.extensions:
> +        #define ${extension.name} 1
> +        % endfor
> +
> +        % for feature in gl_registry.features:
> +        #define ${feature.name} 1
> +        % endfor

You can use arbitrary depth for the mako control flow, I think it's
easier to read if you dedent mako control flow by 2 spaces or so

> +
> +        #endif /*__PIGLIT_DISPATCH_GEN_H__*/'''
> +    ))
> +
> +    c_template = Template(dedent('''\
> +        ${copyright}
> +        % for alias_set in alias_map:
> +        <% f0 = alias_set.primary_command %>
> +        %   for f in alias_set:
> +        <%      cat_list = ' '.join(['(' + cat.name + ')' for cat in
> f.requirements]) %>\\ +        /* ${f.name} ${cat_list} */
> +        %   endfor
> +        static void* resolve_${f0.name}(void)
> +        {
> +        %   for resolution in alias_set.resolutions:
> +        %       if loop.first:
> +            if (${resolution.c_condition}) {
> +        %       else:
> +            } else if (${resolution.c_condition}) {
> +        %       endif
> +                return ${resolution.c_get_proc};
> +        %   endfor
> +            } else {
> +                unsupported("${f0.name}");
> +                return piglit_dispatch_${f0.name};
> +            }
> +        }
> +
> +        static ${f0.c_return_type} APIENTRY
> stub_${f0.name}(${f0.c_named_param_list}) +        {
> +        <%
> +            if f0.c_return_type == 'void':
> +                maybe_return = ''
>              else:
> -                resolve_fn += '\telse if ({0})\n\t\t{1}\n'.format(
> -                    *condition_code_pairs[i])
> -
> -    # Output code to return the dispatch function.
> -    resolve_fn += '\treturn (piglit_dispatch_function_ptr) {0};\n'.format(
> -        ds.dispatch_name)
> -    resolve_fn += '}\n'
> -    return resolve_fn
> -
> -
> -# Generate the stub function for a given DispatchSet.
> -def generate_stub_function(ds):
> -    f0 = ds.primary_function
> -
> -    # Start the stub function
> -    stub_fn = 'static {0}\n'.format(
> -        f0.c_form('APIENTRY ' + ds.stub_name, anonymous_args=False))
> -    stub_fn += '{\n'
> -    stub_fn += '\tcheck_initialized();\n'
> -    stub_fn += '\t{0}();\n'.format(ds.resolve_name)
> -
> -    # Output the call to the dispatch function.
> -    stub_fn += '\t{0}{1}({2});\n'.format(
> -        'return ' if f0.return_type != 'void' else '',
> -        ds.dispatch_name, ', '.join(f0.param_names))
> -    stub_fn += '}\n'
> -    return stub_fn
> -
> -
> -# Generate the reset_dispatch_pointers() function, which sets each
> -# dispatch pointer to point to the corresponding stub function.
> -def generate_dispatch_pointer_resetter(dispatch_sets):
> -    result = []
> -    result.append('static void\n')
> -    result.append('reset_dispatch_pointers()\n')
> -    result.append('{\n')
> -    for ds in dispatch_sets:
> -        result.append(
> -            '\t{0} = {1};\n'.format(ds.dispatch_name, ds.stub_name))
> -    result.append('}\n')
> -    return ''.join(result)
> -
> -
> -# Generate the function_names and function_resolvers tables.
> -def generate_function_names_and_resolvers(dispatch_sets):
> -    name_resolver_pairs = []
> -    for ds in dispatch_sets:
> -        for _, f in ds.cat_fn_pairs:
> -            name_resolver_pairs.append((f.gl_name, ds.resolve_name))
> -    name_resolver_pairs.sort()
> -    result = []
> -    result.append('static const char * const function_names[] = {\n')
> -    for name, _ in name_resolver_pairs:
> -        result.append('\t"{0}",\n'.format(name))
> -    result.append('};\n')
> -    result.append('\n')
> -    result.append('static const piglit_dispatch_resolver_ptr '
> -                  'function_resolvers[] = {\n')
> -    for _, resolver in name_resolver_pairs:
> -        result.append('\t{0},\n'.format(resolver))
> -    result.append('};\n')
> -    return ''.join(result)
> -
> -
> -# Generate the C source and header files for the API.
> -def generate_code(api):
> -    c_contents = [generated_boilerplate()]
> -    h_contents = [generated_boilerplate()]
> -
> -    unique_functions = api.compute_unique_functions()
> -
> -    # Emit typedefs for each name
> -    for f in unique_functions:
> -        h_contents.append(
> -            'typedef {0};\n'.format(
> -                f.c_form('(APIENTRY *{0})'.format(f.typedef_name),
> -                         anonymous_args=True)))
> -
> -    dispatch_sets = api.compute_dispatch_sets()
> -
> -    for ds in dispatch_sets:
> -        f0 = ds.primary_function
> -
> -        # Emit comment block
> -        comments = '\n'
> -        for cat, f in ds.cat_fn_pairs:
> -            comments += '/* {0} ({1}) */\n'.format(f.gl_name, cat)
> -        c_contents.append(comments)
> -        h_contents.append(comments)
> -
> -        # Emit extern declaration of dispatch pointer
> -        h_contents.append(
> -            'extern {0} {1};\n'.format(f0.typedef_name, ds.dispatch_name))
> -
> -        # Emit defines aliasing each GL function to the dispatch
> -        # pointer
> -        for _, f in ds.cat_fn_pairs:
> -            h_contents.append(
> -                '#define {0} {1}\n'.format(f.gl_name, ds.dispatch_name))
> -
> -        # Emit resolve function
> -        c_contents.append(generate_resolve_function(ds))
> -
> -        # Emit stub function
> -        c_contents.append(generate_stub_function(ds))
> -
> -        # Emit initializer for dispatch pointer
> -        c_contents.append(
> -            '{0} {1} = {2};\n'.format(
> -                f0.typedef_name, ds.dispatch_name, ds.stub_name))
> -
> -    # Emit dispatch pointer initialization function
> -    c_contents.append(generate_dispatch_pointer_resetter(dispatch_sets))
> -
> -    c_contents.append('\n')
> -
> -    # Emit function_names and function_resolvers tables.
> -    c_contents.append(generate_function_names_and_resolvers(dispatch_sets))
> -
> -    # Emit enum #defines
> -    for name, value in api.compute_unique_enums():
> -        h_contents.append('#define GL_{0} {1}\n'.format(name, value))
> -
> -    # Emit extension #defines
> -    #
> -    # While enum.ext lists some old extension names (defined to 1), it
> -    # doesn't contain the full set that appears in glext.h.
> -    h_contents.append('\n')
> -    for ext in api.extensions:
> -        h_contents.append('#define {0} 1\n'.format(ext))
> -
> -    # Emit GL version #defines
> -    #
> -    # While enum.ext lists GL versions up to 3.2, it didn't continue
> -    # adding them for later GL versions.
> -    h_contents.append('\n')
> -    for ver in api.gl_versions:
> -        h_contents.append('#define GL_VERSION_{0}_{1} 1\n'.format(
> -            ver // 10, ver % 10))
> -
> -    return ''.join(c_contents), ''.join(h_contents)
> -
> +                maybe_return = 'return '
> +        %>\\
> +            check_initialized();
> +            piglit_dispatch_${f0.name} = resolve_${f0.name}();
> +           
> ${maybe_return}piglit_dispatch_${f0.name}(${f0.c_untyped_param_list}); +   
>     }
> +
> +        PFN${f0.name.upper()}PROC piglit_dispatch_${f0.name} =
> stub_${f0.name}; +        % endfor
> +
> +        static void reset_dispatch_pointers(void)
> +        {
> +        % for alias_set in alias_map:
> +        <% f0 = alias_set.primary_command %>\\
> +            piglit_dispatch_${f0.name} = stub_${f0.name};
> +        % endfor
> +        }
> +
> +        static const char * function_names[] = {
> +        % for f in gl_registry.commands:
> +            "${f.name}",
> +        % endfor
> +        };
> +
> +        static void* (*const function_resolvers[])(void) = {
> +        % for alias_set in alias_map:
> +        <% f0 = alias_set.primary_command %>\\
> +            resolve_${f0.name},
> +        % endfor
> +        };
> +        '''
> +    ))
> +
> +    def __init__(self, gl_registry):
> +        assert(isinstance(gl_registry, registry.gl.Registry))
> +        self.gl_registry = gl_registry
> +        self.alias_map = CommandAliasMap()
> +        self.alias_map.add_commands(gl_registry.commands)
> +
> +    def emit(self, h_buf, c_buf):
> +        for (buf, template) in (
> +                (h_buf, DispatchCode.h_template),
> +                (c_buf, DispatchCode.c_template)):
> +            ctx = mako.runtime.Context(
> +                    buffer=buf,
> +                    gl_registry=self.gl_registry,
> +                    alias_map=self.alias_map,
> +                    copyright=copyright_block,
> +            )
> +            template.render_context(ctx)
> 
>  if __name__ == '__main__':
> -    file_to_parse = sys.argv[1]
> -    api = read_api(file_to_parse)
> -
> -    c_contents, h_contents = generate_code(api)
> -    with open(sys.argv[2], 'w') as f:
> -        f.write(c_contents)
> -    with open(sys.argv[3], 'w') as f:
> -        f.write(h_contents)
> +    main()
> diff --git a/tests/util/piglit-dispatch.h b/tests/util/piglit-dispatch.h
> index c24c186..2d1d5eb 100644
> --- a/tests/util/piglit-dispatch.h
> +++ b/tests/util/piglit-dispatch.h
> @@ -96,6 +96,7 @@ typedef float GLfloat;
>  typedef float GLclampf;
>  typedef double GLdouble;
>  typedef double GLclampd;
> +typedef int32_t GLclampx;
>  typedef void GLvoid;
>  typedef int64_t GLint64EXT;
>  typedef uint64_t GLuint64EXT;
> @@ -136,8 +137,6 @@ typedef piglit_dispatch_function_ptr
> (*piglit_get_core_proc_address_function_ptr
> 
>  typedef piglit_dispatch_function_ptr
> (*piglit_get_ext_proc_address_function_ptr)(const char *);
> 
> -typedef piglit_dispatch_function_ptr (*piglit_dispatch_resolver_ptr)();
> -
>  typedef void (*piglit_error_function_ptr)(const char *);
> 
>  typedef enum {
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.freedesktop.org/archives/piglit/attachments/20140617/f3f84e54/attachment-0001.sig>


More information about the Piglit mailing list