• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2020 The Khronos Group Inc.
4# SPDX-License-Identifier: Apache-2.0
5
6import io,os,re,string,sys
7from lxml import etree
8
9def write(*args, **kwargs):
10    file = kwargs.pop('file', sys.stdout)
11    end = kwargs.pop('end', '\n')
12    file.write(' '.join([str(arg) for arg in args]))
13    file.write(end)
14
15# noneStr - returns string argument, or "" if argument is None.
16# Used in converting lxml Elements into text.
17#   str - string to convert
18def noneStr(str):
19    if (str):
20        return str
21    else:
22        return ""
23
24# matchAPIProfile - returns whether an API and profile
25#   being generated matches an element's profile
26# api - string naming the API to match
27# profile - string naming the profile to match
28# elem - Element which (may) have 'api' and 'profile'
29#   attributes to match to.
30# If a tag is not present in the Element, the corresponding API
31#   or profile always matches.
32# Otherwise, the tag must exactly match the API or profile.
33# Thus, if 'profile' = core:
34#   <remove> with no attribute will match
35#   <remove profile='core'> will match
36#   <remove profile='compatibility'> will not match
37# Possible match conditions:
38#   Requested   Element
39#   Profile     Profile
40#   ---------   --------
41#   None        None        Always matches
42#   'string'    None        Always matches
43#   None        'string'    Does not match. Can't generate multiple APIs
44#                           or profiles, so if an API/profile constraint
45#                           is present, it must be asked for explicitly.
46#   'string'    'string'    Strings must match
47#
48#   ** In the future, we will allow regexes for the attributes,
49#   not just strings, so that api="^(gl|gles2)" will match. Even
50#   this isn't really quite enough, we might prefer something
51#   like "gl(core)|gles1(common-lite)".
52def matchAPIProfile(api, profile, elem):
53    """Match a requested API & profile name to a api & profile attributes of an Element"""
54    match = True
55    # Match 'api', if present
56    if ('api' in elem.attrib):
57        if (api == None):
58            raise UserWarning("No API requested, but 'api' attribute is present with value '" +
59                              elem.get('api') + "'")
60        elif (api != elem.get('api')):
61            # Requested API doesn't match attribute
62            return False
63    if ('profile' in elem.attrib):
64        if (profile == None):
65            raise UserWarning("No profile requested, but 'profile' attribute is present with value '" +
66                elem.get('profile') + "'")
67        elif (profile != elem.get('profile')):
68            # Requested profile doesn't match attribute
69            return False
70    return True
71
72# BaseInfo - base class for information about a registry feature
73# (type/group/enum/command/API/extension).
74#   required - should this feature be defined during header generation
75#     (has it been removed by a profile or version)?
76#   declared - has this feature been defined already?
77#   elem - lxml.etree Element for this feature
78#   resetState() - reset required/declared to initial values. Used
79#     prior to generating a new API interface.
80class BaseInfo:
81    """Represents the state of a registry feature, used during API generation"""
82    def __init__(self, elem):
83        self.required = False
84        self.declared = False
85        self.elem = elem
86    def resetState(self):
87        self.required = False
88        self.declared = False
89
90# TypeInfo - registry information about a type. No additional state
91#   beyond BaseInfo is required.
92class TypeInfo(BaseInfo):
93    """Represents the state of a registry type"""
94    def __init__(self, elem):
95        BaseInfo.__init__(self, elem)
96
97# GroupInfo - registry information about a group of related enums.
98#   enums - dictionary of enum names which are in the group
99class GroupInfo(BaseInfo):
100    """Represents the state of a registry enumerant group"""
101    def __init__(self, elem):
102        BaseInfo.__init__(self, elem)
103        self.enums = {}
104
105# EnumInfo - registry information about an enum
106#   type - numeric type of the value of the <enum> tag
107#     ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )
108class EnumInfo(BaseInfo):
109    """Represents the state of a registry enum"""
110    def __init__(self, elem):
111        BaseInfo.__init__(self, elem)
112        self.type = elem.get('type')
113        if (self.type == None):
114            self.type = ''
115
116# CmdInfo - registry information about a command
117#   glxtype - type of GLX protocol { None, 'render', 'single', 'vendor' }
118#   glxopcode - GLX protocol opcode { None, number }
119#   glxequiv - equivalent command at GLX dispatch level { None, string }
120#   vecequiv - equivalent vector form of a command taking multiple scalar args
121#     { None, string }
122class CmdInfo(BaseInfo):
123    """Represents the state of a registry command"""
124    def __init__(self, elem):
125        BaseInfo.__init__(self, elem)
126        self.glxtype = None
127        self.glxopcode = None
128        self.glxequiv = None
129        self.vecequiv = None
130
131# FeatureInfo - registry information about an API <feature>
132# or <extension>
133#   name - feature name string (e.g. 'GL_ARB_multitexture')
134#   number - feature version number (e.g. 1.2). <extension>
135#     features are unversioned and assigned version number 0.
136#   category - category, e.g. VERSION or ARB/KHR/OES/ETC/vendor
137#   emit - has this feature been defined already?
138class FeatureInfo(BaseInfo):
139    """Represents the state of an API feature (version/extension)"""
140    def __init__(self, elem):
141        BaseInfo.__init__(self, elem)
142        self.name = elem.get('name')
143        # Determine element category (vendor). Only works
144        # for <extension> elements.
145        if (elem.tag == 'feature'):
146            self.category = 'VERSION'
147            self.number = elem.get('number')
148        else:
149            self.category = self.name.split('_', 2)[1]
150            self.number = "0"
151        self.emit = False
152
153# Primary sort key for regSortFeatures.
154# Sorts by category of the feature name string:
155#   Core API features (those defined with a <feature> tag)
156#   ARB/KHR/OES (Khronos extensions)
157#   other       (EXT/vendor extensions)
158def regSortCategoryKey(feature):
159    if (feature.elem.tag == 'feature'):
160        return 0
161    elif (feature.category == 'ARB' or
162          feature.category == 'KHR' or
163          feature.category == 'OES'):
164        return 1
165    else:
166        return 2
167
168# Secondary sort key for regSortFeatures.
169# Sorts by extension name.
170def regSortNameKey(feature):
171    return feature.name
172
173# Tertiary sort key for regSortFeatures.
174# Sorts by feature version number. <extension>
175# elements all have version number "0"
176def regSortNumberKey(feature):
177    return feature.number
178
179# regSortFeatures - default sort procedure for features.
180# Sorts by primary key of feature category,
181# then by feature name within the category,
182# then by version number
183def regSortFeatures(featureList):
184    featureList.sort(key = regSortNumberKey)
185    featureList.sort(key = regSortNameKey)
186    featureList.sort(key = regSortCategoryKey)
187
188# GeneratorOptions - base class for options used during header production
189# These options are target language independent, and used by
190# Registry.apiGen() and by base OutputGenerator objects.
191#
192# Members
193#   filename - name of file to generate, or None to write to stdout.
194#   apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
195#   profile - string specifying API profile , e.g. 'core', or None.
196#   versions - regex matching API versions to process interfaces for.
197#     Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
198#   emitversions - regex matching API versions to actually emit
199#    interfaces for (though all requested versions are considered
200#    when deciding which interfaces to generate). For GL 4.3 glext.h,
201#     this might be '1\.[2-5]|[2-4]\.[0-9]'.
202#   defaultExtensions - If not None, a string which must in its
203#     entirety match the pattern in the "supported" attribute of
204#     the <extension>. Defaults to None. Usually the same as apiname.
205#   addExtensions - regex matching names of additional extensions
206#     to include. Defaults to None.
207#   removeExtensions - regex matching names of extensions to
208#     remove (after defaultExtensions and addExtensions). Defaults
209#     to None.
210#   sortProcedure - takes a list of FeatureInfo objects and sorts
211#     them in place to a preferred order in the generated output.
212#     Default is core API versions, ARB/KHR/OES extensions, all
213#     other extensions, alphabetically within each group.
214# The regex patterns can be None or empty, in which case they match
215#   nothing.
216class GeneratorOptions:
217    """Represents options during header production from an API registry"""
218    def __init__(self,
219                 filename = None,
220                 apiname = None,
221                 profile = None,
222                 versions = '.*',
223                 emitversions = '.*',
224                 defaultExtensions = None,
225                 addExtensions = None,
226                 removeExtensions = None,
227                 sortProcedure = regSortFeatures):
228        self.filename          = filename
229        self.apiname           = apiname
230        self.profile           = profile
231        self.versions          = self.emptyRegex(versions)
232        self.emitversions      = self.emptyRegex(emitversions)
233        self.defaultExtensions = defaultExtensions
234        self.addExtensions     = self.emptyRegex(addExtensions)
235        self.removeExtensions  = self.emptyRegex(removeExtensions)
236        self.sortProcedure     = sortProcedure
237    #
238    # Substitute a regular expression which matches no version
239    # or extension names for None or the empty string.
240    def emptyRegex(self,pat):
241        if (pat == None or pat == ''):
242            return '_nomatch_^'
243        else:
244            return pat
245
246# CGeneratorOptions - subclass of GeneratorOptions.
247#
248# Adds options used by COutputGenerator objects during C language header
249# generation.
250#
251# Additional members
252#   prefixText - list of strings to prefix generated header with
253#     (usually a copyright statement + calling convention macros).
254#   protectFile - True if multiple inclusion protection should be
255#     generated (based on the filename) around the entire header.
256#   protectFeature - True if #ifndef..#endif protection should be
257#     generated around a feature interface in the header file.
258#   genFuncPointers - True if function pointer typedefs should be
259#     generated
260#   protectProto - Controls cpp protection around prototypes:
261#     False - no protection
262#     'nonzero' - protectProtoStr must be defined to a nonzero value
263#     True - protectProtoStr must be defined
264#   protectProtoStr - #ifdef symbol to use around prototype
265#     declarations, if protected
266#   apicall - string to use for the function declaration prefix,
267#     such as APICALL on Windows.
268#   apientry - string to use for the calling convention macro,
269#     in typedefs, such as APIENTRY.
270#   apientryp - string to use for the calling convention macro
271#     in function pointer typedefs, such as APIENTRYP.
272class CGeneratorOptions(GeneratorOptions):
273    """Represents options during C header production from an API registry"""
274    def __init__(self,
275                 filename = None,
276                 apiname = None,
277                 profile = None,
278                 versions = '.*',
279                 emitversions = '.*',
280                 defaultExtensions = None,
281                 addExtensions = None,
282                 removeExtensions = None,
283                 sortProcedure = regSortFeatures,
284                 prefixText = "",
285                 genFuncPointers = True,
286                 protectFile = True,
287                 protectFeature = True,
288                 protectProto = True,
289                 protectProtoStr = True,
290                 apicall = '',
291                 apientry = '',
292                 apientryp = ''):
293        GeneratorOptions.__init__(self, filename, apiname, profile,
294                                  versions, emitversions, defaultExtensions,
295                                  addExtensions, removeExtensions, sortProcedure)
296        self.prefixText      = prefixText
297        self.genFuncPointers = genFuncPointers
298        self.protectFile     = protectFile
299        self.protectFeature  = protectFeature
300        self.protectProto    = protectProto
301        self.protectProtoStr = protectProtoStr
302        self.apicall         = apicall
303        self.apientry        = apientry
304        self.apientryp       = apientryp
305
306# OutputGenerator - base class for generating API interfaces.
307# Manages basic logic, logging, and output file control
308# Derived classes actually generate formatted output.
309#
310# ---- methods ----
311# OutputGenerator(errFile, warnFile, diagFile)
312#   errFile, warnFile, diagFile - file handles to write errors,
313#     warnings, diagnostics to. May be None to not write.
314# logMsg(level, *args) - log messages of different categories
315#   level - 'error', 'warn', or 'diag'. 'error' will also
316#     raise a UserWarning exception
317#   *args - print()-style arguments
318# beginFile(genOpts) - start a new interface file
319#   genOpts - GeneratorOptions controlling what's generated and how
320# endFile() - finish an interface file, closing it when done
321# beginFeature(interface, emit) - write interface for a feature
322# and tag generated features as having been done.
323#   interface - element for the <version> / <extension> to generate
324#   emit - actually write to the header only when True
325# endFeature() - finish an interface.
326# genType(typeinfo,name) - generate interface for a type
327#   typeinfo - TypeInfo for a type
328# genEnum(enuminfo, name) - generate interface for an enum
329#   enuminfo - EnumInfo for an enum
330#   name - enum name
331# genCmd(cmdinfo) - generate interface for a command
332#   cmdinfo - CmdInfo for a command
333class OutputGenerator:
334    """Generate specified API interfaces in a specific style, such as a C header"""
335    def __init__(self,
336                 errFile = sys.stderr,
337                 warnFile = sys.stderr,
338                 diagFile = sys.stdout):
339        self.outFile = None
340        self.errFile = errFile
341        self.warnFile = warnFile
342        self.diagFile = diagFile
343        # Internal state
344        self.featureName = None
345        self.genOpts = None
346    #
347    # logMsg - write a message of different categories to different
348    #   destinations.
349    # level -
350    #   'diag' (diagnostic, voluminous)
351    #   'warn' (warning)
352    #   'error' (fatal error - raises exception after logging)
353    # *args - print()-style arguments to direct to corresponding log
354    def logMsg(self, level, *args):
355        """Log a message at the given level. Can be ignored or log to a file"""
356        if (level == 'error'):
357            strfile = io.StringIO()
358            write('ERROR:', *args, file=strfile)
359            if (self.errFile != None):
360                write(strfile.getvalue(), file=self.errFile)
361            raise UserWarning(strfile.getvalue())
362        elif (level == 'warn'):
363            if (self.warnFile != None):
364                write('WARNING:', *args, file=self.warnFile)
365        elif (level == 'diag'):
366            if (self.diagFile != None):
367                write('DIAG:', *args, file=self.diagFile)
368        else:
369            raise UserWarning(
370                '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
371    #
372    def beginFile(self, genOpts):
373        self.genOpts = genOpts
374        #
375        # Open specified output file. Not done in constructor since a
376        # Generator can be used without writing to a file.
377        if (self.genOpts.filename != None):
378            self.outFile = open(self.genOpts.filename, 'w')
379        else:
380            self.outFile = sys.stdout
381    def endFile(self):
382        self.errFile and self.errFile.flush()
383        self.warnFile and self.warnFile.flush()
384        self.diagFile and self.diagFile.flush()
385        self.outFile.flush()
386        if (self.outFile != sys.stdout and self.outFile != sys.stderr):
387            self.outFile.close()
388        self.genOpts = None
389    #
390    def beginFeature(self, interface, emit):
391        self.emit = emit
392        self.featureName = interface.get('name')
393        # If there's an additional 'protect' attribute in the feature, save it
394        self.featureExtraProtect = interface.get('protect')
395    def endFeature(self):
396        # Derived classes responsible for emitting feature
397        self.featureName = None
398        self.featureExtraProtect = None
399    #
400    # Type generation
401    def genType(self, typeinfo, name):
402        if (self.featureName == None):
403            raise UserWarning('Attempt to generate type', name,
404                    'when not in feature')
405    #
406    # Enumerant generation
407    def genEnum(self, enuminfo, name):
408        if (self.featureName == None):
409            raise UserWarning('Attempt to generate enum', name,
410                    'when not in feature')
411    #
412    # Command generation
413    def genCmd(self, cmd, name):
414        if (self.featureName == None):
415            raise UserWarning('Attempt to generate command', name,
416                    'when not in feature')
417
418# COutputGenerator - subclass of OutputGenerator.
419# Generates C-language API interfaces.
420#
421# ---- methods ----
422# COutputGenerator(errFile, warnFile, diagFile) - args as for
423#   OutputGenerator. Defines additional internal state.
424# makeCDecls(cmd) - return C prototype and function pointer typedef for a
425#     <command> Element, as a list of two strings
426#   cmd - Element for the <command>
427# newline() - print a newline to the output file (utility function)
428# ---- methods overriding base class ----
429# beginFile(genOpts)
430# endFile()
431# beginFeature(interface, emit)
432# endFeature()
433# genType(typeinfo,name) - generate interface for a type
434# genEnum(enuminfo, name)
435# genCmd(cmdinfo)
436class COutputGenerator(OutputGenerator):
437    """Generate specified API interfaces in a specific style, such as a C header"""
438    def __init__(self,
439                 errFile = sys.stderr,
440                 warnFile = sys.stderr,
441                 diagFile = sys.stdout):
442        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
443        # Internal state - accumulators for different inner block text
444        self.typeBody = ''
445        self.enumBody = ''
446        self.cmdBody = ''
447    #
448    # makeCDecls - return C prototype and function pointer typedef for a
449    #   command, as a two-element list of strings.
450    # cmd - Element containing a <command> tag
451    def makeCDecls(self, cmd):
452        """Generate C function pointer typedef for <command> Element"""
453        proto = cmd.find('proto')
454        params = cmd.findall('param')
455        # Begin accumulating prototype and typedef strings
456        pdecl = self.genOpts.apicall
457        tdecl = 'typedef '
458        #
459        # Insert the function return type/name.
460        # For prototypes, add APIENTRY macro before the name
461        # For typedefs, add (APIENTRYP <name>) around the name and
462        #   use the PFNGLCMDNAMEPROC nameng convention.
463        # Done by walking the tree for <proto> element by element.
464        # lxml.etree has elem.text followed by (elem[i], elem[i].tail)
465        #   for each child element and any following text
466        # Leading text
467        pdecl += noneStr(proto.text)
468        tdecl += noneStr(proto.text)
469        # For each child element, if it's a <name> wrap in appropriate
470        # declaration. Otherwise append its contents and tail contents.
471        for elem in proto:
472            text = noneStr(elem.text)
473            tail = noneStr(elem.tail)
474            if (elem.tag == 'name'):
475                pdecl += self.genOpts.apientry + text + tail
476                tdecl += '(' + self.genOpts.apientryp + 'PFN' + text.upper() + 'PROC' + tail + ')'
477            else:
478                pdecl += text + tail
479                tdecl += text + tail
480        # Now add the parameter declaration list, which is identical
481        # for prototypes and typedefs. Concatenate all the text from
482        # a <param> node without the tags. No tree walking required
483        # since all tags are ignored.
484        n = len(params)
485        paramdecl = ' ('
486        if n > 0:
487            for i in range(0,n):
488                paramdecl += ''.join([t for t in params[i].itertext()])
489                if (i < n - 1):
490                    paramdecl += ', '
491        else:
492            paramdecl += 'void'
493        paramdecl += ');\n';
494        return [ pdecl + paramdecl, tdecl + paramdecl ]
495    #
496    def newline(self):
497        write('', file=self.outFile)
498    #
499    def beginFile(self, genOpts):
500        OutputGenerator.beginFile(self, genOpts)
501        # C-specific
502        #
503        # Multiple inclusion protection & C++ wrappers.
504        if (genOpts.protectFile and self.genOpts.filename):
505            headerSym = '__' + self.genOpts.apiname + '_' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
506            write('#ifndef', headerSym, file=self.outFile)
507            write('#define', headerSym, '1', file=self.outFile)
508            self.newline()
509        write('#ifdef __cplusplus', file=self.outFile)
510        write('extern "C" {', file=self.outFile)
511        write('#endif', file=self.outFile)
512        self.newline()
513        #
514        # User-supplied prefix text, if any (list of strings)
515        if (genOpts.prefixText):
516            for s in genOpts.prefixText:
517                write(s, file=self.outFile)
518        #
519        # Some boilerplate describing what was generated - this
520        # will probably be removed later since the extensions
521        # pattern may be very long.
522        write('/* Generated C header for:', file=self.outFile)
523        write(' * API:', genOpts.apiname, file=self.outFile)
524        if (genOpts.profile):
525            write(' * Profile:', genOpts.profile, file=self.outFile)
526        write(' * Versions considered:', genOpts.versions, file=self.outFile)
527        write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
528        write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
529        write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
530        write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
531        write(' */', file=self.outFile)
532    def endFile(self):
533        # C-specific
534        # Finish C++ wrapper and multiple inclusion protection
535        self.newline()
536        write('#ifdef __cplusplus', file=self.outFile)
537        write('}', file=self.outFile)
538        write('#endif', file=self.outFile)
539        if (self.genOpts.protectFile and self.genOpts.filename):
540            self.newline()
541            write('#endif', file=self.outFile)
542        # Finish processing in superclass
543        OutputGenerator.endFile(self)
544    def beginFeature(self, interface, emit):
545        # Start processing in superclass
546        OutputGenerator.beginFeature(self, interface, emit)
547        # C-specific
548        # Accumulate types, enums, function pointer typedefs, end function
549        # prototypes separately for this feature. They're only printed in
550        # endFeature().
551        self.typeBody = ''
552        self.enumBody = ''
553        self.cmdPointerBody = ''
554        self.cmdBody = ''
555    def endFeature(self):
556        # C-specific
557        # Actually write the interface to the output file.
558        if (self.emit):
559            self.newline()
560            if (self.genOpts.protectFeature):
561                write('#ifndef', self.featureName, file=self.outFile)
562            write('#define', self.featureName, '1', file=self.outFile)
563            if (self.typeBody != ''):
564                write(self.typeBody, end='', file=self.outFile)
565            #
566            # Don't add additional protection for derived type declarations,
567            # which may be needed by other features later on.
568            if (self.featureExtraProtect != None):
569                write('#ifdef', self.featureExtraProtect, file=self.outFile)
570            if (self.enumBody != ''):
571                write(self.enumBody, end='', file=self.outFile)
572            if (self.genOpts.genFuncPointers and self.cmdPointerBody != ''):
573                write(self.cmdPointerBody, end='', file=self.outFile)
574            if (self.cmdBody != ''):
575                if (self.genOpts.protectProto == True):
576                    prefix = '#ifdef ' + self.genOpts.protectProtoStr + '\n'
577                    suffix = '#endif\n'
578                elif (self.genOpts.protectProto == 'nonzero'):
579                    prefix = '#if ' + self.genOpts.protectProtoStr + '\n'
580                    suffix = '#endif\n'
581                elif (self.genOpts.protectProto == False):
582                    prefix = ''
583                    suffix = ''
584                else:
585                    self.gen.logMsg('warn',
586                                    '*** Unrecognized value for protectProto:',
587                                    self.genOpts.protectProto,
588                                    'not generating prototype wrappers')
589                    prefix = ''
590                    suffix = ''
591
592                write(prefix + self.cmdBody + suffix, end='', file=self.outFile)
593            if (self.featureExtraProtect != None):
594                write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
595            if (self.genOpts.protectFeature):
596                write('#endif /*', self.featureName, '*/', file=self.outFile)
597        # Finish processing in superclass
598        OutputGenerator.endFeature(self)
599    #
600    # Type generation
601    def genType(self, typeinfo, name):
602        OutputGenerator.genType(self, typeinfo, name)
603        #
604        # Replace <apientry /> tags with an APIENTRY-style string
605        # (from self.genOpts). Copy other text through unchanged.
606        # If the resulting text is an empty string, don't emit it.
607        typeElem = typeinfo.elem
608        s = noneStr(typeElem.text)
609        for elem in typeElem:
610            if (elem.tag == 'apientry'):
611                s += self.genOpts.apientry + noneStr(elem.tail)
612            else:
613                s += noneStr(elem.text) + noneStr(elem.tail)
614        if (len(s) > 0):
615            self.typeBody += s + '\n'
616    #
617    # Enumerant generation
618    def genEnum(self, enuminfo, name):
619        OutputGenerator.genEnum(self, enuminfo, name)
620        #
621        # EnumInfo.type is a C value suffix (e.g. u, ull)
622        self.enumBody += '#define ' + name.ljust(33) + ' ' + enuminfo.elem.get('value')
623        #
624        # Handle non-integer 'type' fields by using it as the C value suffix
625        t = enuminfo.elem.get('type')
626        if (t != '' and t != 'i'):
627            self.enumBody += enuminfo.type
628        self.enumBody += '\n'
629    #
630    # Command generation
631    def genCmd(self, cmdinfo, name):
632        OutputGenerator.genCmd(self, cmdinfo, name)
633        #
634        decls = self.makeCDecls(cmdinfo.elem)
635        self.cmdBody += decls[0]
636        if (self.genOpts.genFuncPointers):
637            self.cmdPointerBody += decls[1]
638
639# Registry - object representing an API registry, loaded from an XML file
640# Members
641#   tree - ElementTree containing the root <registry>
642#   typedict - dictionary of TypeInfo objects keyed by type name
643#   groupdict - dictionary of GroupInfo objects keyed by group name
644#   enumdict - dictionary of EnumInfo objects keyed by enum name
645#   cmddict - dictionary of CmdInfo objects keyed by command name
646#   apidict - dictionary of <api> Elements keyed by API name
647#   extensions - list of <extension> Elements
648#   extdict - dictionary of <extension> Elements keyed by extension name
649#   gen - OutputGenerator object used to write headers / messages
650#   genOpts - GeneratorOptions object used to control which
651#     fetures to write and how to format them
652#   emitFeatures - True to actually emit features for a version / extension,
653#     or False to just treat them as emitted
654# Public methods
655#   loadElementTree(etree) - load registry from specified ElementTree
656#   loadFile(filename) - load registry from XML file
657#   setGenerator(gen) - OutputGenerator to use
658#   parseTree() - parse the registry once loaded & create dictionaries
659#   dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries
660#     to specified file handle (default stdout). Truncates type /
661#     enum / command elements to maxlen characters (default 80)
662#   generator(g) - specify the output generator object
663#   apiGen(apiname, genOpts) - generate API headers for the API type
664#     and profile specified in genOpts, but only for the versions and
665#     extensions specified there.
666#   apiReset() - call between calls to apiGen() to reset internal state
667#   validateGroups() - call to verify that each <proto> or <param>
668#     with a 'group' attribute matches an actual existing group.
669# Private methods
670#   addElementInfo(elem,info,infoName,dictionary) - add feature info to dict
671#   lookupElementInfo(fname,dictionary) - lookup feature info in dict
672class Registry:
673    """Represents an API registry loaded from XML"""
674    def __init__(self):
675        self.tree         = None
676        self.typedict     = {}
677        self.groupdict    = {}
678        self.enumdict     = {}
679        self.cmddict      = {}
680        self.apidict      = {}
681        self.extensions   = []
682        self.extdict      = {}
683        # A default output generator, so commands prior to apiGen can report
684        # errors via the generator object.
685        self.gen          = OutputGenerator()
686        self.genOpts      = None
687        self.emitFeatures = False
688    def loadElementTree(self, tree):
689        """Load ElementTree into a Registry object and parse it"""
690        self.tree = tree
691        self.parseTree()
692    def loadFile(self, file):
693        """Load an API registry XML file into a Registry object and parse it"""
694        self.tree = etree.parse(file)
695        self.parseTree()
696    def setGenerator(self, gen):
697        """Specify output generator object. None restores the default generator"""
698        self.gen = gen
699    # addElementInfo - add information about an element to the
700    # corresponding dictionary
701    #   elem - <type>/<group>/<enum>/<command>/<feature>/<extension> Element
702    #   info - corresponding {Type|Group|Enum|Cmd|Feature}Info object
703    #   infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension'
704    #   dictionary - self.{type|group|enum|cmd|api|ext}dict
705    # If the Element has an 'api' attribute, the dictionary key is the
706    # tuple (name,api). If not, the key is the name. 'name' is an
707    # attribute of the Element
708    def addElementInfo(self, elem, info, infoName, dictionary):
709        if ('api' in elem.attrib):
710            key = (elem.get('name'),elem.get('api'))
711        else:
712            key = elem.get('name')
713        if key in dictionary:
714            self.gen.logMsg('warn', '*** Attempt to redefine',
715                            infoName, 'with key:', key)
716        else:
717            dictionary[key] = info
718    #
719    # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name.
720    # If an object qualified by API name exists, use that.
721    #   fname - name of type / enum / command
722    #   dictionary - self.{type|enum|cmd}dict
723    def lookupElementInfo(self, fname, dictionary):
724        key = (fname, self.genOpts.apiname)
725        if (key in dictionary):
726            # self.gen.logMsg('diag', 'Found API-specific element for feature', fname)
727            return dictionary[key]
728        elif (fname in dictionary):
729            # self.gen.logMsg('diag', 'Found generic element for feature', fname)
730            return dictionary[fname]
731        else:
732            return None
733    def parseTree(self):
734        """Parse the registry Element, once created"""
735        # This must be the Element for the root <registry>
736        self.reg = self.tree.getroot()
737        #
738        # Create dictionary of registry types from toplevel <types> tags
739        # and add 'name' attribute to each <type> tag (where missing)
740        # based on its <name> element.
741        #
742        # There's usually one <types> block; more are OK
743        # Required <type> attributes: 'name' or nested <name> tag contents
744        self.typedict = {}
745        for type in self.reg.findall('types/type'):
746            # If the <type> doesn't already have a 'name' attribute, set
747            # it from contents of its <name> tag.
748            if (type.get('name') == None):
749                type.attrib['name'] = type.find('name').text
750            self.addElementInfo(type, TypeInfo(type), 'type', self.typedict)
751        #
752        # Create dictionary of registry groups from toplevel <groups> tags.
753        #
754        # There's usually one <groups> block; more are OK.
755        # Required <group> attributes: 'name'
756        self.groupdict = {}
757        for group in self.reg.findall('groups/group'):
758            self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict)
759        #
760        # Create dictionary of registry enums from toplevel <enums> tags
761        #
762        # There are usually many <enums> tags in different namespaces, but
763        #   these are functional namespaces of the values, while the actual
764        #   enum names all share the dictionary.
765        # Required <enums> attributes: 'name', 'value'
766        self.enumdict = {}
767        for enum in self.reg.findall('enums/enum'):
768            self.addElementInfo(enum, EnumInfo(enum), 'enum', self.enumdict)
769        #
770        # Create dictionary of registry commands from <command> tags
771        # and add 'name' attribute to each <command> tag (where missing)
772        # based on its <proto><name> element.
773        #
774        # There's usually only one <commands> block; more are OK.
775        # Required <command> attributes: 'name' or <proto><name> tag contents
776        self.cmddict = {}
777        for cmd in self.reg.findall('commands/command'):
778            # If the <command> doesn't already have a 'name' attribute, set
779            # it from contents of its <proto><name> tag.
780            if (cmd.get('name') == None):
781                cmd.attrib['name'] = cmd.find('proto/name').text
782            ci = CmdInfo(cmd)
783            self.addElementInfo(cmd, ci, 'command', self.cmddict)
784        #
785        # Create dictionaries of API and extension interfaces
786        #   from toplevel <api> and <extension> tags.
787        #
788        self.apidict = {}
789        for feature in self.reg.findall('feature'):
790            ai = FeatureInfo(feature)
791            self.addElementInfo(feature, ai, 'feature', self.apidict)
792        self.extensions = self.reg.findall('extensions/extension')
793        self.extdict = {}
794        for feature in self.extensions:
795            ei = FeatureInfo(feature)
796            self.addElementInfo(feature, ei, 'extension', self.extdict)
797    def dumpReg(self, maxlen = 40, filehandle = sys.stdout):
798        """Dump all the dictionaries constructed from the Registry object"""
799        write('***************************************', file=filehandle)
800        write('    ** Dumping Registry contents **',     file=filehandle)
801        write('***************************************', file=filehandle)
802        write('// Types', file=filehandle)
803        for name in self.typedict:
804            tobj = self.typedict[name]
805            write('    Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle)
806        write('// Groups', file=filehandle)
807        for name in self.groupdict:
808            gobj = self.groupdict[name]
809            write('    Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle)
810        write('// Enums', file=filehandle)
811        for name in self.enumdict:
812            eobj = self.enumdict[name]
813            write('    Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle)
814        write('// Commands', file=filehandle)
815        for name in self.cmddict:
816            cobj = self.cmddict[name]
817            write('    Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle)
818        write('// APIs', file=filehandle)
819        for key in self.apidict:
820            write('    API Version ', key, '->',
821                etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle)
822        write('// Extensions', file=filehandle)
823        for key in self.extdict:
824            write('    Extension', key, '->',
825                etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle)
826        # write('***************************************', file=filehandle)
827        # write('    ** Dumping XML ElementTree **', file=filehandle)
828        # write('***************************************', file=filehandle)
829        # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle)
830    #
831    # typename - name of type
832    # required - boolean (to tag features as required or not)
833    def markTypeRequired(self, typename, required):
834        """Require (along with its dependencies) or remove (but not its dependencies) a type"""
835        self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required)
836        # Get TypeInfo object for <type> tag corresponding to typename
837        type = self.lookupElementInfo(typename, self.typedict)
838        if (type != None):
839            # Tag required type dependencies as required.
840            # This DOES NOT un-tag dependencies in a <remove> tag.
841            # See comments in markRequired() below for the reason.
842            if (required and ('requires' in type.elem.attrib)):
843                depType = type.elem.get('requires')
844                self.gen.logMsg('diag', '*** Generating dependent type',
845                    depType, 'for type', typename)
846                self.markTypeRequired(depType, required)
847            type.required = required
848        else:
849            self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED')
850    #
851    # features - Element for <require> or <remove> tag
852    # required - boolean (to tag features as required or not)
853    def markRequired(self, features, required):
854        """Require or remove features specified in the Element"""
855        self.gen.logMsg('diag', '*** markRequired (features = <too long to print>, required =', required, ')')
856        # Loop over types, enums, and commands in the tag
857        # @@ It would be possible to respect 'api' and 'profile' attributes
858        #  in individual features, but that's not done yet.
859        for typeElem in features.findall('type'):
860            self.markTypeRequired(typeElem.get('name'), required)
861        for enumElem in features.findall('enum'):
862            name = enumElem.get('name')
863            self.gen.logMsg('diag', '*** tagging enum:', name, '-> required =', required)
864            enum = self.lookupElementInfo(name, self.enumdict)
865            if (enum != None):
866                enum.required = required
867            else:
868                self.gen.logMsg('warn', '*** enum:', name , 'IS NOT DEFINED')
869        for cmdElem in features.findall('command'):
870            name = cmdElem.get('name')
871            self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required)
872            cmd = self.lookupElementInfo(name, self.cmddict)
873            if (cmd != None):
874                cmd.required = required
875                # Tag all parameter types of this command as required.
876                # This DOES NOT remove types of commands in a <remove>
877                # tag, because many other commands may use the same type.
878                # We could be more clever and reference count types,
879                # instead of using a boolean.
880                if (required):
881                    # Look for <ptype> in entire <command> tree,
882                    # not just immediate children
883                    for ptype in cmd.elem.findall('.//ptype'):
884                        self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', ptype.text)
885                        self.markTypeRequired(ptype.text, required)
886            else:
887                self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED')
888    #
889    # interface - Element for <version> or <extension>, containing
890    #   <require> and <remove> tags
891    # api - string specifying API name being generated
892    # profile - string specifying API profile being generated
893    def requireAndRemoveFeatures(self, interface, api, profile):
894        """Process <recquire> and <remove> tags for a <version> or <extension>"""
895        # <require> marks things that are required by this version/profile
896        for feature in interface.findall('require'):
897            if (matchAPIProfile(api, profile, feature)):
898                self.markRequired(feature,True)
899        # <remove> marks things that are removed by this version/profile
900        for feature in interface.findall('remove'):
901            if (matchAPIProfile(api, profile, feature)):
902                self.markRequired(feature,False)
903    #
904    # generateFeature - generate a single type / enum / command,
905    # and all its dependencies as needed.
906    #   fname - name of feature (<type>/<enum>/<command>
907    #   ftype - type of feature, 'type' | 'enum' | 'command'
908    #   dictionary - of *Info objects - self.{type|enum|cmd}dict
909    #   genProc - bound function pointer for self.gen.gen{Type|Enum|Cmd}
910    def generateFeature(self, fname, ftype, dictionary, genProc):
911        f = self.lookupElementInfo(fname, dictionary)
912        if (f == None):
913            # No such feature. This is an error, but reported earlier
914            self.gen.logMsg('diag', '*** No entry found for feature', fname,
915                            'returning!')
916            return
917        #
918        # If feature isn't required, or has already been declared, return
919        if (not f.required):
920            self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)')
921            return
922        if (f.declared):
923            self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)')
924            return
925        #
926        # Pull in dependent type declaration(s) of the feature.
927        # For types, there may be one in the 'required' attribute of the element
928        # For commands, there may be many in <ptype> tags within the element
929        # For enums, no dependencies are allowed (though perhasps if you
930        #   have a uint64 enum, it should require GLuint64)
931        if (ftype == 'type'):
932            if ('requires' in f.elem.attrib):
933                depname = f.elem.get('requires')
934                self.gen.logMsg('diag', '*** Generating required dependent type',
935                                depname)
936                self.generateFeature(depname, 'type', self.typedict,
937                                     self.gen.genType)
938        elif (ftype == 'command'):
939            for ptype in f.elem.findall('.//ptype'):
940                depname = ptype.text
941                self.gen.logMsg('diag', '*** Generating required parameter type',
942                                depname)
943                self.generateFeature(depname, 'type', self.typedict,
944                                     self.gen.genType)
945        #
946        # Actually generate the type only if emitting declarations
947        if self.emitFeatures:
948            self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname)
949            genProc(f, fname)
950        else:
951            self.gen.logMsg('diag', '*** Skipping', ftype, fname,
952                            '(not emitting this feature)')
953        # Always mark feature declared, as though actually emitted
954        f.declared = True
955    #
956    # generateRequiredInterface - generate all interfaces required
957    # by an API version or extension
958    #   interface - Element for <version> or <extension>
959    def generateRequiredInterface(self, interface):
960        """Generate required C interface for specified API version/extension"""
961        #
962        # Loop over all features inside all <require> tags.
963        # <remove> tags are ignored (handled in pass 1).
964        for features in interface.findall('require'):
965            for t in features.findall('type'):
966                self.generateFeature(t.get('name'), 'type', self.typedict,
967                                     self.gen.genType)
968            for e in features.findall('enum'):
969                self.generateFeature(e.get('name'), 'enum', self.enumdict,
970                                     self.gen.genEnum)
971            for c in features.findall('command'):
972                self.generateFeature(c.get('name'), 'command', self.cmddict,
973                                     self.gen.genCmd)
974    #
975    # apiGen(genOpts) - generate interface for specified versions
976    #   genOpts - GeneratorOptions object with parameters used
977    #   by the Generator object.
978    def apiGen(self, genOpts):
979        """Generate interfaces for the specified API type and range of versions"""
980        #
981        self.gen.logMsg('diag', '*******************************************')
982        self.gen.logMsg('diag', '  Registry.apiGen file:', genOpts.filename,
983                        'api:', genOpts.apiname,
984                        'profile:', genOpts.profile)
985        self.gen.logMsg('diag', '*******************************************')
986        #
987        self.genOpts = genOpts
988        # Reset required/declared flags for all features
989        self.apiReset()
990        #
991        # Compile regexps used to select versions & extensions
992        regVersions = re.compile(self.genOpts.versions)
993        regEmitVersions = re.compile(self.genOpts.emitversions)
994        regAddExtensions = re.compile(self.genOpts.addExtensions)
995        regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
996        #
997        # Get all matching API versions & add to list of FeatureInfo
998        features = []
999        apiMatch = False
1000        for key in self.apidict:
1001            fi = self.apidict[key]
1002            api = fi.elem.get('api')
1003            if (api == self.genOpts.apiname):
1004                apiMatch = True
1005                if (regVersions.match(fi.number)):
1006                    # Matches API & version #s being generated. Mark for
1007                    # emission and add to the features[] list .
1008                    # @@ Could use 'declared' instead of 'emit'?
1009                    fi.emit = (regEmitVersions.match(fi.number) != None)
1010                    features.append(fi)
1011                    if (not fi.emit):
1012                        self.gen.logMsg('diag', '*** NOT tagging feature api =', api,
1013                            'name =', fi.name, 'number =', fi.number,
1014                            'for emission (does not match emitversions pattern)')
1015                else:
1016                    self.gen.logMsg('diag', '*** NOT including feature api =', api,
1017                        'name =', fi.name, 'number =', fi.number,
1018                        '(does not match requested versions)')
1019            else:
1020                self.gen.logMsg('diag', '*** NOT including feature api =', api,
1021                    'name =', fi.name,
1022                    '(does not match requested API)')
1023        if (not apiMatch):
1024            self.gen.logMsg('warn', '*** No matching API versions found!')
1025        #
1026        # Get all matching extensions & add to the list.
1027        # Start with extensions tagged with 'api' pattern matching the API
1028        # being generated. Add extensions matching the pattern specified in
1029        # regExtensions, then remove extensions matching the pattern
1030        # specified in regRemoveExtensions
1031        for key in self.extdict:
1032            ei = self.extdict[key]
1033            extName = ei.name
1034            include = False
1035            #
1036            # Include extension if defaultExtensions is not None and if the
1037            # 'supported' attribute matches defaultExtensions. The regexp in
1038            # 'supported' must exactly match defaultExtensions, so bracket
1039            # it with ^(pat)$.
1040            pat = '^(' + ei.elem.get('supported') + ')$'
1041            if (self.genOpts.defaultExtensions and
1042                     re.match(pat, self.genOpts.defaultExtensions)):
1043                self.gen.logMsg('diag', '*** Including extension',
1044                    extName, "(defaultExtensions matches the 'supported' attribute)")
1045                include = True
1046            #
1047            # Include additional extensions if the extension name matches
1048            # the regexp specified in the generator options. This allows
1049            # forcing extensions into an interface even if they're not
1050            # tagged appropriately in the registry.
1051            if (regAddExtensions.match(extName) != None):
1052                self.gen.logMsg('diag', '*** Including extension',
1053                    extName, '(matches explicitly requested extensions to add)')
1054                include = True
1055            # Remove extensions if the name matches the regexp specified
1056            # in generator options. This allows forcing removal of
1057            # extensions from an interface even if they're tagged that
1058            # way in the registry.
1059            if (regRemoveExtensions.match(extName) != None):
1060                self.gen.logMsg('diag', '*** Removing extension',
1061                    extName, '(matches explicitly requested extensions to remove)')
1062                include = False
1063            #
1064            # If the extension is to be included, add it to the
1065            # extension features list.
1066            if (include):
1067                ei.emit = True
1068                features.append(ei)
1069            else:
1070                self.gen.logMsg('diag', '*** NOT including extension',
1071                    extName, '(does not match api attribute or explicitly requested extensions)')
1072        #
1073        # Sort the extension features list, if a sort procedure is defined
1074        if (self.genOpts.sortProcedure):
1075            self.genOpts.sortProcedure(features)
1076        #
1077        # Pass 1: loop over requested API versions and extensions tagging
1078        #   types/commands/features as required (in an <require> block) or no
1079        #   longer required (in an <exclude> block). It is possible to remove
1080        #   a feature in one version and restore it later by requiring it in
1081        #   a later version.
1082        # If a profile other than 'None' is being generated, it must
1083        #   match the profile attribute (if any) of the <require> and
1084        #   <remove> tags.
1085        self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************')
1086        for f in features:
1087            self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for',
1088                f.name)
1089            self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile)
1090        #
1091        # Pass 2: loop over specified API versions and extensions printing
1092        #   declarations for required things which haven't already been
1093        #   generated.
1094        self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************')
1095        self.gen.beginFile(self.genOpts)
1096        for f in features:
1097            self.gen.logMsg('diag', '*** PASS 2: Generating interface for',
1098                f.name)
1099            emit = self.emitFeatures = f.emit
1100            if (not emit):
1101                self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature',
1102                    f.elem.get('name'), 'because it is not tagged for emission')
1103            # Generate the interface (or just tag its elements as having been
1104            # emitted, if they haven't been).
1105            self.gen.beginFeature(f.elem, emit)
1106            self.generateRequiredInterface(f.elem)
1107            self.gen.endFeature()
1108        self.gen.endFile()
1109    #
1110    # apiReset - use between apiGen() calls to reset internal state
1111    #
1112    def apiReset(self):
1113        """Reset type/enum/command dictionaries before generating another API"""
1114        for type in self.typedict:
1115            self.typedict[type].resetState()
1116        for enum in self.enumdict:
1117            self.enumdict[enum].resetState()
1118        for cmd in self.cmddict:
1119            self.cmddict[cmd].resetState()
1120        for cmd in self.apidict:
1121            self.apidict[cmd].resetState()
1122    #
1123    # validateGroups - check that group= attributes match actual groups
1124    #
1125    def validateGroups(self):
1126        """Validate group= attributes on <param> and <proto> tags"""
1127        # Keep track of group names not in <group> tags
1128        badGroup = {}
1129        self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***')
1130        for cmd in self.reg.findall('commands/command'):
1131            proto = cmd.find('proto')
1132            funcname = cmd.find('proto/name').text
1133            if ('group' in proto.attrib.keys()):
1134                group = proto.get('group')
1135                # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group)
1136                if (group not in self.groupdict.keys()):
1137                    # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group)
1138                    if (group not in badGroup.keys()):
1139                        badGroup[group] = 1
1140                    else:
1141                        badGroup[group] = badGroup[group] +  1
1142            for param in cmd.findall('param'):
1143                pname = param.find('name')
1144                if (pname != None):
1145                    pname = pname.text
1146                else:
1147                    pname = type.get('name')
1148                if ('group' in param.attrib.keys()):
1149                    group = param.get('group')
1150                    if (group not in self.groupdict.keys()):
1151                        # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group)
1152                        if (group not in badGroup.keys()):
1153                            badGroup[group] = 1
1154                        else:
1155                            badGroup[group] = badGroup[group] +  1
1156        if (len(badGroup.keys()) > 0):
1157            self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***')
1158            for key in sorted(badGroup.keys()):
1159                self.gen.logMsg('diag', '    ', key, ' occurred ', badGroup[key], ' times')
1160