1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2013-2018 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 17from __future__ import unicode_literals 18import io,os,re,sys 19 20def write( *args, **kwargs ): 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) 25 26# noneStr - returns string argument, or "" if argument is None. 27# Used in converting etree Elements into text. 28# str - string to convert 29def 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. 37def enquote(str): 38 if (str): 39 return "'" + str + "'" 40 else: 41 return None 42 43# apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a 44# function pointer type), False otherwise. 45def apiName(str): 46 return str[0:2].lower() == 'vk' or str[0:3] == 'PFN' 47 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! 54def 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. 66def 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" 71def 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. 76def 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) 83def 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 93# filename - basename of file to generate, or None to write to stdout. 94# directory - directory in which to generate filename 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. 117class GeneratorOptions: 118 """Represents options during header production from an API registry""" 119 def __init__(self, 120 filename = None, 121 directory = '.', 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 131 self.directory = directory 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 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. 163# makeDir(directory) - create a directory, if not already done. 164# Generally called from derived generators creating hierarchies. 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 184# isEnumRequired(enumElem) - return True if this <enum> element is required 185# elem - <enum> element to test 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# 191class OutputGenerator: 192 """Generate specified API interfaces in a specific style, such as a C header""" 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 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 220 self.madeDirs = {} 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 # 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 # 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): 322 filename = self.genOpts.directory + '/' + self.genOpts.filename 323 self.outFile = io.open(filename, 'w', encoding='utf-8') 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) 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) 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) 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) + ' ' 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 # 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 # 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. 452 # etree has elem.text followed by (elem[i], elem[i].tail) 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 # 508