Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 1 | #!/usr/bin/python3 -i |
Dustin Graves | 3ff520c | 2016-03-28 16:17:38 -0600 | [diff] [blame] | 2 | # |
Mark Lobodzinski | dbe7dce | 2018-01-08 08:17:24 -0700 | [diff] [blame] | 3 | # Copyright (c) 2013-2018 The Khronos Group Inc. |
Dustin Graves | 3ff520c | 2016-03-28 16:17:38 -0600 | [diff] [blame] | 4 | # |
Jon Ashburn | 3ebf125 | 2016-04-19 11:30:31 -0600 | [diff] [blame] | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
Dustin Graves | 3ff520c | 2016-03-28 16:17:38 -0600 | [diff] [blame] | 8 | # |
Jon Ashburn | 3ebf125 | 2016-04-19 11:30:31 -0600 | [diff] [blame] | 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
Dustin Graves | 3ff520c | 2016-03-28 16:17:38 -0600 | [diff] [blame] | 10 | # |
Jon Ashburn | 3ebf125 | 2016-04-19 11:30:31 -0600 | [diff] [blame] | 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
Dustin Graves | 3ff520c | 2016-03-28 16:17:38 -0600 | [diff] [blame] | 16 | |
Mark Lobodzinski | bc54f2c | 2017-03-21 10:12:31 -0600 | [diff] [blame] | 17 | from __future__ import unicode_literals |
Mark Lobodzinski | cbaa2cd | 2016-12-19 09:41:16 -0700 | [diff] [blame] | 18 | import io,os,re,sys |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 19 | |
| 20 | def write( *args, **kwargs ): |
Mark Lobodzinski | bc54f2c | 2017-03-21 10:12:31 -0600 | [diff] [blame] | 21 | file = kwargs.pop('file',sys.stdout) |
| 22 | end = kwargs.pop('end','\n') |
| 23 | file.write(' '.join([str(arg) for arg in args])) |
| 24 | file.write(end) |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 25 | |
| 26 | # noneStr - returns string argument, or "" if argument is None. |
Mike Stroyan | 3c5a6e2 | 2016-04-05 16:40:30 -0600 | [diff] [blame] | 27 | # Used in converting etree Elements into text. |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 28 | # str - string to convert |
| 29 | def noneStr(str): |
| 30 | if (str): |
| 31 | return str |
| 32 | else: |
| 33 | return "" |
| 34 | |
| 35 | # enquote - returns string argument with surrounding quotes, |
| 36 | # for serialization into Python code. |
| 37 | def enquote(str): |
| 38 | if (str): |
| 39 | return "'" + str + "'" |
| 40 | else: |
| 41 | return None |
| 42 | |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 43 | # apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a |
| 44 | # function pointer type), False otherwise. |
| 45 | def apiName(str): |
| 46 | return str[0:2].lower() == 'vk' or str[0:3] == 'PFN' |
| 47 | |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 48 | # Primary sort key for regSortFeatures. |
| 49 | # Sorts by category of the feature name string: |
| 50 | # Core API features (those defined with a <feature> tag) |
| 51 | # ARB/KHR/OES (Khronos extensions) |
| 52 | # other (EXT/vendor extensions) |
| 53 | # This will need changing for Vulkan! |
| 54 | def regSortCategoryKey(feature): |
| 55 | if (feature.elem.tag == 'feature'): |
| 56 | return 0 |
| 57 | elif (feature.category == 'ARB' or |
| 58 | feature.category == 'KHR' or |
| 59 | feature.category == 'OES'): |
| 60 | return 1 |
| 61 | else: |
| 62 | return 2 |
| 63 | |
| 64 | # Secondary sort key for regSortFeatures. |
| 65 | # Sorts by extension name. |
| 66 | def regSortNameKey(feature): |
| 67 | return feature.name |
| 68 | |
| 69 | # Second sort key for regSortFeatures. |
| 70 | # Sorts by feature version. <extension> elements all have version number "0" |
| 71 | def regSortFeatureVersionKey(feature): |
| 72 | return float(feature.version) |
| 73 | |
| 74 | # Tertiary sort key for regSortFeatures. |
| 75 | # Sorts by extension number. <feature> elements all have extension number 0. |
| 76 | def regSortExtensionNumberKey(feature): |
| 77 | return int(feature.number) |
| 78 | |
| 79 | # regSortFeatures - default sort procedure for features. |
| 80 | # Sorts by primary key of feature category ('feature' or 'extension') |
| 81 | # then by version number (for features) |
| 82 | # then by extension number (for extensions) |
| 83 | def regSortFeatures(featureList): |
| 84 | featureList.sort(key = regSortExtensionNumberKey) |
| 85 | featureList.sort(key = regSortFeatureVersionKey) |
| 86 | featureList.sort(key = regSortCategoryKey) |
| 87 | |
| 88 | # GeneratorOptions - base class for options used during header production |
| 89 | # These options are target language independent, and used by |
| 90 | # Registry.apiGen() and by base OutputGenerator objects. |
| 91 | # |
| 92 | # Members |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 93 | # filename - basename of file to generate, or None to write to stdout. |
| 94 | # directory - directory in which to generate filename |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 95 | # apiname - string matching <api> 'apiname' attribute, e.g. 'gl'. |
| 96 | # profile - string specifying API profile , e.g. 'core', or None. |
| 97 | # versions - regex matching API versions to process interfaces for. |
| 98 | # Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. |
| 99 | # emitversions - regex matching API versions to actually emit |
| 100 | # interfaces for (though all requested versions are considered |
| 101 | # when deciding which interfaces to generate). For GL 4.3 glext.h, |
| 102 | # this might be '1\.[2-5]|[2-4]\.[0-9]'. |
| 103 | # defaultExtensions - If not None, a string which must in its |
| 104 | # entirety match the pattern in the "supported" attribute of |
| 105 | # the <extension>. Defaults to None. Usually the same as apiname. |
| 106 | # addExtensions - regex matching names of additional extensions |
| 107 | # to include. Defaults to None. |
| 108 | # removeExtensions - regex matching names of extensions to |
| 109 | # remove (after defaultExtensions and addExtensions). Defaults |
| 110 | # to None. |
| 111 | # sortProcedure - takes a list of FeatureInfo objects and sorts |
| 112 | # them in place to a preferred order in the generated output. |
| 113 | # Default is core API versions, ARB/KHR/OES extensions, all |
| 114 | # other extensions, alphabetically within each group. |
| 115 | # The regex patterns can be None or empty, in which case they match |
| 116 | # nothing. |
| 117 | class GeneratorOptions: |
| 118 | """Represents options during header production from an API registry""" |
| 119 | def __init__(self, |
| 120 | filename = None, |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 121 | directory = '.', |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 122 | apiname = None, |
| 123 | profile = None, |
| 124 | versions = '.*', |
| 125 | emitversions = '.*', |
| 126 | defaultExtensions = None, |
| 127 | addExtensions = None, |
| 128 | removeExtensions = None, |
| 129 | sortProcedure = regSortFeatures): |
| 130 | self.filename = filename |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 131 | self.directory = directory |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 132 | self.apiname = apiname |
| 133 | self.profile = profile |
| 134 | self.versions = self.emptyRegex(versions) |
| 135 | self.emitversions = self.emptyRegex(emitversions) |
| 136 | self.defaultExtensions = defaultExtensions |
| 137 | self.addExtensions = self.emptyRegex(addExtensions) |
| 138 | self.removeExtensions = self.emptyRegex(removeExtensions) |
| 139 | self.sortProcedure = sortProcedure |
| 140 | # |
| 141 | # Substitute a regular expression which matches no version |
| 142 | # or extension names for None or the empty string. |
| 143 | def emptyRegex(self,pat): |
| 144 | if (pat == None or pat == ''): |
| 145 | return '_nomatch_^' |
| 146 | else: |
| 147 | return pat |
| 148 | |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 149 | # OutputGenerator - base class for generating API interfaces. |
| 150 | # Manages basic logic, logging, and output file control |
| 151 | # Derived classes actually generate formatted output. |
| 152 | # |
| 153 | # ---- methods ---- |
| 154 | # OutputGenerator(errFile, warnFile, diagFile) |
| 155 | # errFile, warnFile, diagFile - file handles to write errors, |
| 156 | # warnings, diagnostics to. May be None to not write. |
| 157 | # logMsg(level, *args) - log messages of different categories |
| 158 | # level - 'error', 'warn', or 'diag'. 'error' will also |
| 159 | # raise a UserWarning exception |
| 160 | # *args - print()-style arguments |
| 161 | # setExtMap(map) - specify a dictionary map from extension names to |
| 162 | # numbers, used in creating values for extension enumerants. |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 163 | # makeDir(directory) - create a directory, if not already done. |
| 164 | # Generally called from derived generators creating hierarchies. |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 165 | # beginFile(genOpts) - start a new interface file |
| 166 | # genOpts - GeneratorOptions controlling what's generated and how |
| 167 | # endFile() - finish an interface file, closing it when done |
| 168 | # beginFeature(interface, emit) - write interface for a feature |
| 169 | # and tag generated features as having been done. |
| 170 | # interface - element for the <version> / <extension> to generate |
| 171 | # emit - actually write to the header only when True |
| 172 | # endFeature() - finish an interface. |
| 173 | # genType(typeinfo,name) - generate interface for a type |
| 174 | # typeinfo - TypeInfo for a type |
| 175 | # genStruct(typeinfo,name) - generate interface for a C "struct" type. |
| 176 | # typeinfo - TypeInfo for a type interpreted as a struct |
| 177 | # genGroup(groupinfo,name) - generate interface for a group of enums (C "enum") |
| 178 | # groupinfo - GroupInfo for a group |
| 179 | # genEnum(enuminfo, name) - generate interface for an enum (constant) |
| 180 | # enuminfo - EnumInfo for an enum |
| 181 | # name - enum name |
| 182 | # genCmd(cmdinfo) - generate interface for a command |
| 183 | # cmdinfo - CmdInfo for a command |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 184 | # isEnumRequired(enumElem) - return True if this <enum> element is required |
| 185 | # elem - <enum> element to test |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 186 | # makeCDecls(cmd) - return C prototype and function pointer typedef for a |
| 187 | # <command> Element, as a list of two strings |
| 188 | # cmd - Element for the <command> |
| 189 | # newline() - print a newline to the output file (utility function) |
| 190 | # |
| 191 | class OutputGenerator: |
| 192 | """Generate specified API interfaces in a specific style, such as a C header""" |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 193 | # |
| 194 | # categoryToPath - map XML 'category' to include file directory name |
| 195 | categoryToPath = { |
| 196 | 'bitmask' : 'flags', |
| 197 | 'enum' : 'enums', |
| 198 | 'funcpointer' : 'funcpointers', |
| 199 | 'handle' : 'handles', |
| 200 | 'define' : 'defines', |
| 201 | 'basetype' : 'basetypes', |
| 202 | } |
| 203 | # |
| 204 | # Constructor |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 205 | def __init__(self, |
| 206 | errFile = sys.stderr, |
| 207 | warnFile = sys.stderr, |
| 208 | diagFile = sys.stdout): |
| 209 | self.outFile = None |
| 210 | self.errFile = errFile |
| 211 | self.warnFile = warnFile |
| 212 | self.diagFile = diagFile |
| 213 | # Internal state |
| 214 | self.featureName = None |
| 215 | self.genOpts = None |
| 216 | self.registry = None |
| 217 | # Used for extension enum value generation |
| 218 | self.extBase = 1000000000 |
| 219 | self.extBlockSize = 1000 |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 220 | self.madeDirs = {} |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 221 | # |
| 222 | # logMsg - write a message of different categories to different |
| 223 | # destinations. |
| 224 | # level - |
| 225 | # 'diag' (diagnostic, voluminous) |
| 226 | # 'warn' (warning) |
| 227 | # 'error' (fatal error - raises exception after logging) |
| 228 | # *args - print()-style arguments to direct to corresponding log |
| 229 | def logMsg(self, level, *args): |
| 230 | """Log a message at the given level. Can be ignored or log to a file""" |
| 231 | if (level == 'error'): |
| 232 | strfile = io.StringIO() |
| 233 | write('ERROR:', *args, file=strfile) |
| 234 | if (self.errFile != None): |
| 235 | write(strfile.getvalue(), file=self.errFile) |
| 236 | raise UserWarning(strfile.getvalue()) |
| 237 | elif (level == 'warn'): |
| 238 | if (self.warnFile != None): |
| 239 | write('WARNING:', *args, file=self.warnFile) |
| 240 | elif (level == 'diag'): |
| 241 | if (self.diagFile != None): |
| 242 | write('DIAG:', *args, file=self.diagFile) |
| 243 | else: |
| 244 | raise UserWarning( |
| 245 | '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) |
| 246 | # |
| 247 | # enumToValue - parses and converts an <enum> tag into a value. |
| 248 | # Returns a list |
| 249 | # first element - integer representation of the value, or None |
| 250 | # if needsNum is False. The value must be a legal number |
| 251 | # if needsNum is True. |
| 252 | # second element - string representation of the value |
| 253 | # There are several possible representations of values. |
| 254 | # A 'value' attribute simply contains the value. |
| 255 | # A 'bitpos' attribute defines a value by specifying the bit |
| 256 | # position which is set in that value. |
| 257 | # A 'offset','extbase','extends' triplet specifies a value |
| 258 | # as an offset to a base value defined by the specified |
| 259 | # 'extbase' extension name, which is then cast to the |
| 260 | # typename specified by 'extends'. This requires probing |
| 261 | # the registry database, and imbeds knowledge of the |
| 262 | # Vulkan extension enum scheme in this function. |
| 263 | def enumToValue(self, elem, needsNum): |
| 264 | name = elem.get('name') |
| 265 | numVal = None |
| 266 | if ('value' in elem.keys()): |
| 267 | value = elem.get('value') |
| 268 | # print('About to translate value =', value, 'type =', type(value)) |
| 269 | if (needsNum): |
| 270 | numVal = int(value, 0) |
| 271 | # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or |
| 272 | # 'ull'), append it to the string value. |
| 273 | # t = enuminfo.elem.get('type') |
| 274 | # if (t != None and t != '' and t != 'i' and t != 's'): |
| 275 | # value += enuminfo.type |
| 276 | self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') |
| 277 | return [numVal, value] |
| 278 | if ('bitpos' in elem.keys()): |
| 279 | value = elem.get('bitpos') |
| 280 | numVal = int(value, 0) |
| 281 | numVal = 1 << numVal |
| 282 | value = '0x%08x' % numVal |
| 283 | self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') |
| 284 | return [numVal, value] |
| 285 | if ('offset' in elem.keys()): |
| 286 | # Obtain values in the mapping from the attributes |
| 287 | enumNegative = False |
| 288 | offset = int(elem.get('offset'),0) |
| 289 | extnumber = int(elem.get('extnumber'),0) |
| 290 | extends = elem.get('extends') |
| 291 | if ('dir' in elem.keys()): |
| 292 | enumNegative = True |
| 293 | self.logMsg('diag', 'Enum', name, 'offset =', offset, |
| 294 | 'extnumber =', extnumber, 'extends =', extends, |
| 295 | 'enumNegative =', enumNegative) |
| 296 | # Now determine the actual enumerant value, as defined |
| 297 | # in the "Layers and Extensions" appendix of the spec. |
| 298 | numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset |
| 299 | if (enumNegative): |
| 300 | numVal = -numVal |
| 301 | value = '%d' % numVal |
| 302 | # More logic needed! |
| 303 | self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') |
| 304 | return [numVal, value] |
| 305 | return [None, None] |
| 306 | # |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 307 | def makeDir(self, path): |
| 308 | self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') |
| 309 | if not (path in self.madeDirs.keys()): |
| 310 | # This can get race conditions with multiple writers, see |
| 311 | # https://stackoverflow.com/questions/273192/ |
| 312 | if not os.path.exists(path): |
| 313 | os.makedirs(path) |
| 314 | self.madeDirs[path] = None |
| 315 | # |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 316 | def beginFile(self, genOpts): |
| 317 | self.genOpts = genOpts |
| 318 | # |
| 319 | # Open specified output file. Not done in constructor since a |
| 320 | # Generator can be used without writing to a file. |
| 321 | if (self.genOpts.filename != None): |
Mark Lobodzinski | 2d58982 | 2016-12-12 09:44:34 -0700 | [diff] [blame] | 322 | filename = self.genOpts.directory + '/' + self.genOpts.filename |
Mark Lobodzinski | cbaa2cd | 2016-12-19 09:41:16 -0700 | [diff] [blame] | 323 | self.outFile = io.open(filename, 'w', encoding='utf-8') |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 324 | else: |
| 325 | self.outFile = sys.stdout |
| 326 | def endFile(self): |
| 327 | self.errFile and self.errFile.flush() |
| 328 | self.warnFile and self.warnFile.flush() |
| 329 | self.diagFile and self.diagFile.flush() |
| 330 | self.outFile.flush() |
| 331 | if (self.outFile != sys.stdout and self.outFile != sys.stderr): |
| 332 | self.outFile.close() |
| 333 | self.genOpts = None |
| 334 | # |
| 335 | def beginFeature(self, interface, emit): |
| 336 | self.emit = emit |
| 337 | self.featureName = interface.get('name') |
| 338 | # If there's an additional 'protect' attribute in the feature, save it |
| 339 | self.featureExtraProtect = interface.get('protect') |
| 340 | def endFeature(self): |
| 341 | # Derived classes responsible for emitting feature |
| 342 | self.featureName = None |
| 343 | self.featureExtraProtect = None |
| 344 | # Utility method to validate we're generating something only inside a |
| 345 | # <feature> tag |
| 346 | def validateFeature(self, featureType, featureName): |
| 347 | if (self.featureName == None): |
| 348 | raise UserWarning('Attempt to generate', featureType, name, |
| 349 | 'when not in feature') |
| 350 | # |
| 351 | # Type generation |
| 352 | def genType(self, typeinfo, name): |
| 353 | self.validateFeature('type', name) |
| 354 | # |
| 355 | # Struct (e.g. C "struct" type) generation |
| 356 | def genStruct(self, typeinfo, name): |
| 357 | self.validateFeature('struct', name) |
Mark Lobodzinski | e4f2c5f | 2017-07-17 14:26:47 -0600 | [diff] [blame] | 358 | |
| 359 | # The mixed-mode <member> tags may contain no-op <comment> tags. |
| 360 | # It is convenient to remove them here where all output generators |
| 361 | # will benefit. |
| 362 | for member in typeinfo.elem.findall('.//member'): |
| 363 | for comment in member.findall('comment'): |
| 364 | member.remove(comment) |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 365 | # |
| 366 | # Group (e.g. C "enum" type) generation |
| 367 | def genGroup(self, groupinfo, name): |
| 368 | self.validateFeature('group', name) |
| 369 | # |
| 370 | # Enumerant (really, constant) generation |
| 371 | def genEnum(self, enuminfo, name): |
| 372 | self.validateFeature('enum', name) |
| 373 | # |
| 374 | # Command generation |
| 375 | def genCmd(self, cmd, name): |
| 376 | self.validateFeature('command', name) |
| 377 | # |
| 378 | # Utility functions - turn a <proto> <name> into C-language prototype |
| 379 | # and typedef declarations for that name. |
| 380 | # name - contents of <name> tag |
| 381 | # tail - whatever text follows that tag in the Element |
| 382 | def makeProtoName(self, name, tail): |
| 383 | return self.genOpts.apientry + name + tail |
| 384 | def makeTypedefName(self, name, tail): |
| 385 | return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' |
| 386 | # |
| 387 | # makeCParamDecl - return a string which is an indented, formatted |
| 388 | # declaration for a <param> or <member> block (e.g. function parameter |
| 389 | # or structure/union member). |
| 390 | # param - Element (<param> or <member>) to format |
| 391 | # aligncol - if non-zero, attempt to align the nested <name> element |
| 392 | # at this column |
| 393 | def makeCParamDecl(self, param, aligncol): |
| 394 | paramdecl = ' ' + noneStr(param.text) |
| 395 | for elem in param: |
| 396 | text = noneStr(elem.text) |
| 397 | tail = noneStr(elem.tail) |
| 398 | if (elem.tag == 'name' and aligncol > 0): |
| 399 | self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) |
| 400 | # Align at specified column, if possible |
| 401 | paramdecl = paramdecl.rstrip() |
| 402 | oldLen = len(paramdecl) |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 403 | # This works around a problem where very long type names - |
| 404 | # longer than the alignment column - would run into the tail |
| 405 | # text. |
| 406 | paramdecl = paramdecl.ljust(aligncol-1) + ' ' |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 407 | newLen = len(paramdecl) |
| 408 | self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) |
| 409 | paramdecl += text + tail |
| 410 | return paramdecl |
| 411 | # |
| 412 | # getCParamTypeLength - return the length of the type field is an indented, formatted |
| 413 | # declaration for a <param> or <member> block (e.g. function parameter |
| 414 | # or structure/union member). |
| 415 | # param - Element (<param> or <member>) to identify |
| 416 | def getCParamTypeLength(self, param): |
| 417 | paramdecl = ' ' + noneStr(param.text) |
| 418 | for elem in param: |
| 419 | text = noneStr(elem.text) |
| 420 | tail = noneStr(elem.tail) |
| 421 | if (elem.tag == 'name'): |
| 422 | # Align at specified column, if possible |
| 423 | newLen = len(paramdecl.rstrip()) |
| 424 | self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) |
| 425 | paramdecl += text + tail |
| 426 | return newLen |
| 427 | # |
Mark Lobodzinski | c97e264 | 2016-10-13 08:58:38 -0600 | [diff] [blame] | 428 | # isEnumRequired(elem) - return True if this <enum> element is |
| 429 | # required, False otherwise |
| 430 | # elem - <enum> element to test |
| 431 | def isEnumRequired(self, elem): |
| 432 | return (elem.get('extname') is None or |
| 433 | re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or |
| 434 | self.genOpts.defaultExtensions == elem.get('supported')) |
| 435 | # |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 436 | # makeCDecls - return C prototype and function pointer typedef for a |
| 437 | # command, as a two-element list of strings. |
| 438 | # cmd - Element containing a <command> tag |
| 439 | def makeCDecls(self, cmd): |
| 440 | """Generate C function pointer typedef for <command> Element""" |
| 441 | proto = cmd.find('proto') |
| 442 | params = cmd.findall('param') |
| 443 | # Begin accumulating prototype and typedef strings |
| 444 | pdecl = self.genOpts.apicall |
| 445 | tdecl = 'typedef ' |
| 446 | # |
| 447 | # Insert the function return type/name. |
| 448 | # For prototypes, add APIENTRY macro before the name |
| 449 | # For typedefs, add (APIENTRY *<name>) around the name and |
| 450 | # use the PFN_cmdnameproc naming convention. |
| 451 | # Done by walking the tree for <proto> element by element. |
Mike Stroyan | 3c5a6e2 | 2016-04-05 16:40:30 -0600 | [diff] [blame] | 452 | # etree has elem.text followed by (elem[i], elem[i].tail) |
Mike Stroyan | dee76ef | 2016-01-07 15:35:37 -0700 | [diff] [blame] | 453 | # for each child element and any following text |
| 454 | # Leading text |
| 455 | pdecl += noneStr(proto.text) |
| 456 | tdecl += noneStr(proto.text) |
| 457 | # For each child element, if it's a <name> wrap in appropriate |
| 458 | # declaration. Otherwise append its contents and tail contents. |
| 459 | for elem in proto: |
| 460 | text = noneStr(elem.text) |
| 461 | tail = noneStr(elem.tail) |
| 462 | if (elem.tag == 'name'): |
| 463 | pdecl += self.makeProtoName(text, tail) |
| 464 | tdecl += self.makeTypedefName(text, tail) |
| 465 | else: |
| 466 | pdecl += text + tail |
| 467 | tdecl += text + tail |
| 468 | # Now add the parameter declaration list, which is identical |
| 469 | # for prototypes and typedefs. Concatenate all the text from |
| 470 | # a <param> node without the tags. No tree walking required |
| 471 | # since all tags are ignored. |
| 472 | # Uses: self.indentFuncProto |
| 473 | # self.indentFuncPointer |
| 474 | # self.alignFuncParam |
| 475 | # Might be able to doubly-nest the joins, e.g. |
| 476 | # ','.join(('_'.join([l[i] for i in range(0,len(l))]) |
| 477 | n = len(params) |
| 478 | # Indented parameters |
| 479 | if n > 0: |
| 480 | indentdecl = '(\n' |
| 481 | for i in range(0,n): |
| 482 | paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam) |
| 483 | if (i < n - 1): |
| 484 | paramdecl += ',\n' |
| 485 | else: |
| 486 | paramdecl += ');' |
| 487 | indentdecl += paramdecl |
| 488 | else: |
| 489 | indentdecl = '(void);' |
| 490 | # Non-indented parameters |
| 491 | paramdecl = '(' |
| 492 | if n > 0: |
| 493 | for i in range(0,n): |
| 494 | paramdecl += ''.join([t for t in params[i].itertext()]) |
| 495 | if (i < n - 1): |
| 496 | paramdecl += ', ' |
| 497 | else: |
| 498 | paramdecl += 'void' |
| 499 | paramdecl += ");"; |
| 500 | return [ pdecl + indentdecl, tdecl + paramdecl ] |
| 501 | # |
| 502 | def newline(self): |
| 503 | write('', file=self.outFile) |
| 504 | |
| 505 | def setRegistry(self, registry): |
| 506 | self.registry = registry |
| 507 | # |