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