1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6"""Base class for source/header/doc generators, as well as some utility functions.""" 7 8from __future__ import unicode_literals 9 10import io 11import os 12import pdb 13import re 14import shutil 15import sys 16import tempfile 17try: 18 from pathlib import Path 19except ImportError: 20 from pathlib2 import Path # type: ignore 21 22from spec_tools.util import getElemName, getElemType 23 24 25def write(*args, **kwargs): 26 file = kwargs.pop('file', sys.stdout) 27 end = kwargs.pop('end', '\n') 28 file.write(' '.join(str(arg) for arg in args)) 29 file.write(end) 30 31 32def noneStr(s): 33 """Return string argument, or "" if argument is None. 34 35 Used in converting etree Elements into text. 36 s - string to convert""" 37 if s: 38 return s 39 return "" 40 41 42def enquote(s): 43 """Return string argument with surrounding quotes, 44 for serialization into Python code.""" 45 if s: 46 if isinstance(s, str): 47 return f"'{s}'" 48 else: 49 return s 50 return None 51 52 53def regSortCategoryKey(feature): 54 """Sort key for regSortFeatures. 55 Sorts by category of the feature name string: 56 57 - Core API features (those defined with a `<feature>` tag) 58 - (sort VKSC after VK - this is Vulkan-specific) 59 - ARB/KHR/OES (Khronos extensions) 60 - other (EXT/vendor extensions)""" 61 62 if feature.elem.tag == 'feature': 63 if feature.name.startswith('VKSC'): 64 return 0.5 65 else: 66 return 0 67 if (feature.category == 'ARB' 68 or feature.category == 'KHR' 69 or feature.category == 'OES'): 70 return 1 71 72 return 2 73 74 75def regSortOrderKey(feature): 76 """Sort key for regSortFeatures - key is the sortorder attribute.""" 77 78 return feature.sortorder 79 80 81def regSortNameKey(feature): 82 """Sort key for regSortFeatures - key is the extension name.""" 83 84 return feature.name 85 86 87def regSortFeatureVersionKey(feature): 88 """Sort key for regSortFeatures - key is the feature version. 89 `<extension>` elements all have version number 0.""" 90 91 return float(feature.versionNumber) 92 93 94def regSortExtensionNumberKey(feature): 95 """Sort key for regSortFeatures - key is the extension number. 96 `<feature>` elements all have extension number 0.""" 97 98 return int(feature.number) 99 100 101def regSortFeatures(featureList): 102 """Default sort procedure for features. 103 104 - Sorts by explicit sort order (default 0) relative to other features 105 - then by feature category ('feature' or 'extension'), 106 - then by version number (for features) 107 - then by extension number (for extensions)""" 108 featureList.sort(key=regSortExtensionNumberKey) 109 featureList.sort(key=regSortFeatureVersionKey) 110 featureList.sort(key=regSortCategoryKey) 111 featureList.sort(key=regSortOrderKey) 112 113 114class MissingGeneratorOptionsError(RuntimeError): 115 """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None.""" 116 117 def __init__(self, msg=None): 118 full_msg = 'Missing generator options object self.genOpts' 119 if msg: 120 full_msg += ': ' + msg 121 super().__init__(full_msg) 122 123 124class MissingRegistryError(RuntimeError): 125 """Error raised when a Generator tries to do something that requires a Registry object but it is None.""" 126 127 def __init__(self, msg=None): 128 full_msg = 'Missing Registry object self.registry' 129 if msg: 130 full_msg += ': ' + msg 131 super().__init__(full_msg) 132 133 134class MissingGeneratorOptionsConventionsError(RuntimeError): 135 """Error raised when a Generator tries to do something that requires a Conventions object but it is None.""" 136 137 def __init__(self, msg=None): 138 full_msg = 'Missing Conventions object self.genOpts.conventions' 139 if msg: 140 full_msg += ': ' + msg 141 super().__init__(full_msg) 142 143 144class GeneratorOptions: 145 """Base class for options used during header/documentation production. 146 147 These options are target language independent, and used by 148 Registry.apiGen() and by base OutputGenerator objects.""" 149 150 def __init__(self, 151 conventions=None, 152 filename=None, 153 directory='.', 154 genpath=None, 155 apiname=None, 156 mergeApiNames=None, 157 profile=None, 158 versions='.*', 159 emitversions='.*', 160 defaultExtensions=None, 161 addExtensions=None, 162 removeExtensions=None, 163 emitExtensions=None, 164 emitSpirv=None, 165 emitFormats=None, 166 reparentEnums=True, 167 sortProcedure=regSortFeatures, 168 requireCommandAliases=False, 169 requireDepends=True, 170 ): 171 """Constructor. 172 173 Arguments: 174 175 - conventions - may be mandatory for some generators: 176 an object that implements ConventionsBase 177 - filename - basename of file to generate, or None to write to stdout. 178 - directory - directory in which to generate filename 179 - genpath - path to previously generated files, such as apimap.py 180 - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'. 181 - mergeApiNames - If not None, a comma separated list of API names 182 to merge into the API specified by 'apiname' 183 - profile - string specifying API profile , e.g. 'core', or None. 184 - versions - regex matching API versions to process interfaces for. 185 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions. 186 - emitversions - regex matching API versions to actually emit 187 interfaces for (though all requested versions are considered 188 when deciding which interfaces to generate). For GL 4.3 glext.h, 189 this might be `'1[.][2-5]|[2-4][.][0-9]'`. 190 - defaultExtensions - If not None, a string which must in its 191 entirety match the pattern in the "supported" attribute of 192 the `<extension>`. Defaults to None. Usually the same as apiname. 193 - addExtensions - regex matching names of additional extensions 194 to include. Defaults to None. 195 - removeExtensions - regex matching names of extensions to 196 remove (after defaultExtensions and addExtensions). Defaults 197 to None. 198 - emitExtensions - regex matching names of extensions to actually emit 199 interfaces for (though all requested versions are considered when 200 deciding which interfaces to generate). Defaults to None. 201 - emitSpirv - regex matching names of extensions and capabilities 202 to actually emit interfaces for. 203 - emitFormats - regex matching names of formats to actually emit 204 interfaces for. 205 - reparentEnums - move <enum> elements which extend an enumerated 206 type from <feature> or <extension> elements to the target <enums> 207 element. This is required for almost all purposes, but the 208 InterfaceGenerator relies on the list of interfaces in the <feature> 209 or <extension> being complete. Defaults to True. 210 - sortProcedure - takes a list of FeatureInfo objects and sorts 211 them in place to a preferred order in the generated output. 212 - requireCommandAliases - if True, treat command aliases 213 as required dependencies. 214 - requireDepends - whether to follow API dependencies when emitting 215 APIs. 216 217 Default is 218 - core API versions 219 - Khronos (ARB/KHR/OES) extensions 220 - All other extensions 221 - By core API version number or extension number in each group. 222 223 The regex patterns can be None or empty, in which case they match 224 nothing.""" 225 self.conventions = conventions 226 """may be mandatory for some generators: 227 an object that implements ConventionsBase""" 228 229 self.filename = filename 230 "basename of file to generate, or None to write to stdout." 231 232 self.genpath = genpath 233 """path to previously generated files, such as apimap.py""" 234 235 self.directory = directory 236 "directory in which to generate filename" 237 238 self.apiname = apiname 239 "string matching `<api>` 'apiname' attribute, e.g. 'gl'." 240 241 self.mergeApiNames = mergeApiNames 242 "comma separated list of API names to merge into the API specified by 'apiname'" 243 244 self.profile = profile 245 "string specifying API profile , e.g. 'core', or None." 246 247 self.versions = self.emptyRegex(versions) 248 """regex matching API versions to process interfaces for. 249 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.""" 250 251 self.emitversions = self.emptyRegex(emitversions) 252 """regex matching API versions to actually emit 253 interfaces for (though all requested versions are considered 254 when deciding which interfaces to generate). For GL 4.3 glext.h, 255 this might be `'1[.][2-5]|[2-4][.][0-9]'`.""" 256 257 self.defaultExtensions = defaultExtensions 258 """If not None, a string which must in its 259 entirety match the pattern in the "supported" attribute of 260 the `<extension>`. Defaults to None. Usually the same as apiname.""" 261 262 self.addExtensions = self.emptyRegex(addExtensions) 263 """regex matching names of additional extensions 264 to include. Defaults to None.""" 265 266 self.removeExtensions = self.emptyRegex(removeExtensions) 267 """regex matching names of extensions to 268 remove (after defaultExtensions and addExtensions). Defaults 269 to None.""" 270 271 self.emitExtensions = self.emptyRegex(emitExtensions) 272 """regex matching names of extensions to actually emit 273 interfaces for (though all requested versions are considered when 274 deciding which interfaces to generate).""" 275 276 self.emitSpirv = self.emptyRegex(emitSpirv) 277 """regex matching names of extensions and capabilities 278 to actually emit interfaces for.""" 279 280 self.emitFormats = self.emptyRegex(emitFormats) 281 """regex matching names of formats 282 to actually emit interfaces for.""" 283 284 self.reparentEnums = reparentEnums 285 """boolean specifying whether to remove <enum> elements from 286 <feature> or <extension> when extending an <enums> type.""" 287 288 self.sortProcedure = sortProcedure 289 """takes a list of FeatureInfo objects and sorts 290 them in place to a preferred order in the generated output. 291 Default is core API versions, ARB/KHR/OES extensions, all 292 other extensions, alphabetically within each group.""" 293 294 self.codeGenerator = False 295 """True if this generator makes compilable code""" 296 297 self.registry = None 298 """Populated later with the registry object.""" 299 300 self.requireCommandAliases = requireCommandAliases 301 """True if alias= attributes of <command> tags are transitively 302 required.""" 303 304 self.requireDepends = requireDepends 305 """True if dependencies of API tags are transitively required.""" 306 307 def emptyRegex(self, pat): 308 """Substitute a regular expression which matches no version 309 or extension names for None or the empty string.""" 310 if not pat: 311 return '_nomatch_^' 312 313 return pat 314 315 316class OutputGenerator: 317 """Generate specified API interfaces in a specific style, such as a C header. 318 319 Base class for generating API interfaces. 320 Manages basic logic, logging, and output file control. 321 Derived classes actually generate formatted output. 322 """ 323 324 # categoryToPath - map XML 'category' to include file directory name 325 categoryToPath = { 326 'bitmask': 'flags', 327 'enum': 'enums', 328 'funcpointer': 'funcpointers', 329 'handle': 'handles', 330 'define': 'defines', 331 'basetype': 'basetypes', 332 } 333 334 def breakName(self, name, msg): 335 """Break into debugger if this is a special name""" 336 337 # List of string names to break on 338 bad = ( 339 ) 340 341 if name in bad and True: 342 print('breakName {}: {}'.format(name, msg)) 343 pdb.set_trace() 344 345 def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout): 346 """Constructor 347 348 - errFile, warnFile, diagFile - file handles to write errors, 349 warnings, diagnostics to. May be None to not write.""" 350 self.outFile = None 351 self.errFile = errFile 352 self.warnFile = warnFile 353 self.diagFile = diagFile 354 # Internal state 355 self.featureName = None 356 """The current feature name being generated.""" 357 358 self.featureType = None 359 """The current feature type being generated.""" 360 361 self.genOpts = None 362 """The GeneratorOptions subclass instance.""" 363 364 self.registry = None 365 """The specification registry object.""" 366 367 self.featureDictionary = {} 368 """The dictionary of dictionaries of API features.""" 369 370 # Used for extension enum value generation 371 self.extBase = 1000000000 372 self.extBlockSize = 1000 373 self.madeDirs = {} 374 375 # API dictionary, which may be loaded by the beginFile method of 376 # derived generators. 377 self.apidict = None 378 379 # File suffix for generated files, set in beginFile below. 380 self.file_suffix = '' 381 382 def logMsg(self, level, *args): 383 """Write a message of different categories to different 384 destinations. 385 386 - `level` 387 - 'diag' (diagnostic, voluminous) 388 - 'warn' (warning) 389 - 'error' (fatal error - raises exception after logging) 390 391 - `*args` - print()-style arguments to direct to corresponding log""" 392 if level == 'error': 393 strfile = io.StringIO() 394 write('ERROR:', *args, file=strfile) 395 if self.errFile is not None: 396 write(strfile.getvalue(), file=self.errFile) 397 raise UserWarning(strfile.getvalue()) 398 elif level == 'warn': 399 if self.warnFile is not None: 400 write('WARNING:', *args, file=self.warnFile) 401 elif level == 'diag': 402 if self.diagFile is not None: 403 write('DIAG:', *args, file=self.diagFile) 404 else: 405 raise UserWarning( 406 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) 407 408 def enumToValue(self, elem, needsNum, bitwidth = 32, 409 forceSuffix = False, parent_for_alias_dereference=None): 410 """Parse and convert an `<enum>` tag into a value. 411 412 - elem - <enum> Element 413 - needsNum - generate a numeric representation of the element value 414 - bitwidth - size of the numeric representation in bits (32 or 64) 415 - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers 416 - parent_for_alias_dereference - if not None, an Element containing 417 the parent of elem, used to look for elements this is an alias of 418 419 Returns a list: 420 421 - first element - integer representation of the value, or None 422 if needsNum is False. The value must be a legal number 423 if needsNum is True. 424 - second element - string representation of the value 425 426 There are several possible representations of values. 427 428 - A 'value' attribute simply contains the value. 429 - A 'bitpos' attribute defines a value by specifying the bit 430 position which is set in that value. 431 - An 'offset','extbase','extends' triplet specifies a value 432 as an offset to a base value defined by the specified 433 'extbase' extension name, which is then cast to the 434 typename specified by 'extends'. This requires probing 435 the registry database, and imbeds knowledge of the 436 API extension enum scheme in this function. 437 - An 'alias' attribute contains the name of another enum 438 which this is an alias of. The other enum must be 439 declared first when emitting this enum.""" 440 if self.genOpts is None: 441 raise MissingGeneratorOptionsError() 442 if self.genOpts.conventions is None: 443 raise MissingGeneratorOptionsConventionsError() 444 445 name = elem.get('name') 446 numVal = None 447 if 'value' in elem.keys(): 448 value = elem.get('value') 449 # print('About to translate value =', value, 'type =', type(value)) 450 if needsNum: 451 numVal = int(value, 0) 452 # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or 453 # 'ull'), append it to the string value. 454 # t = enuminfo.elem.get('type') 455 # if t is not None and t != '' and t != 'i' and t != 's': 456 # value += enuminfo.type 457 if forceSuffix: 458 if bitwidth == 64: 459 value = value + 'ULL' 460 else: 461 value = value + 'U' 462 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') 463 return [numVal, value] 464 if 'bitpos' in elem.keys(): 465 value = elem.get('bitpos') 466 bitpos = int(value, 0) 467 numVal = 1 << bitpos 468 value = '0x%08x' % numVal 469 if bitwidth == 64 or bitpos >= 32: 470 value = value + 'ULL' 471 elif forceSuffix: 472 value = value + 'U' 473 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') 474 return [numVal, value] 475 if 'offset' in elem.keys(): 476 # Obtain values in the mapping from the attributes 477 enumNegative = False 478 offset = int(elem.get('offset'), 0) 479 extnumber = int(elem.get('extnumber'), 0) 480 extends = elem.get('extends') 481 if 'dir' in elem.keys(): 482 enumNegative = True 483 self.logMsg('diag', 'Enum', name, 'offset =', offset, 484 'extnumber =', extnumber, 'extends =', extends, 485 'enumNegative =', enumNegative) 486 # Now determine the actual enumerant value, as defined 487 # in the "Layers and Extensions" appendix of the spec. 488 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset 489 if enumNegative: 490 numVal *= -1 491 value = '%d' % numVal 492 # More logic needed! 493 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') 494 return [numVal, value] 495 if 'alias' in elem.keys(): 496 alias_of = elem.get('alias') 497 if parent_for_alias_dereference is None: 498 return (None, alias_of) 499 siblings = parent_for_alias_dereference.findall('enum') 500 for sib in siblings: 501 sib_name = sib.get('name') 502 if sib_name == alias_of: 503 return self.enumToValue(sib, needsNum) 504 raise RuntimeError("Could not find the aliased enum value") 505 return [None, None] 506 507 def checkDuplicateEnums(self, enums): 508 """Check enumerated values for duplicates. 509 510 - enums - list of `<enum>` Elements 511 512 returns the list with duplicates stripped""" 513 # Dictionaries indexed by name and numeric value. 514 # Entries are [ Element, numVal, strVal ] matching name or value 515 516 nameMap = {} 517 valueMap = {} 518 519 stripped = [] 520 for elem in enums: 521 name = elem.get('name') 522 (numVal, strVal) = self.enumToValue(elem, True) 523 524 if name in nameMap: 525 # Duplicate name found; check values 526 (name2, numVal2, strVal2) = nameMap[name] 527 528 # Duplicate enum values for the same name are benign. This 529 # happens when defining the same enum conditionally in 530 # several extension blocks. 531 if (strVal2 == strVal or (numVal is not None 532 and numVal == numVal2)): 533 True 534 # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name + 535 # ') found with the same value:' + strVal) 536 else: 537 self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name 538 + ') found with different values:' + strVal 539 + ' and ' + strVal2) 540 541 # Do not add the duplicate to the returned list 542 continue 543 elif numVal in valueMap: 544 # Duplicate value found (such as an alias); report it, but 545 # still add this enum to the list. 546 (name2, numVal2, strVal2) = valueMap[numVal] 547 548 msg = 'Two enums found with the same value: {} = {} = {}'.format( 549 name, name2.get('name'), strVal) 550 self.logMsg('error', msg) 551 552 # Track this enum to detect followon duplicates 553 nameMap[name] = [elem, numVal, strVal] 554 if numVal is not None: 555 valueMap[numVal] = [elem, numVal, strVal] 556 557 # Add this enum to the list 558 stripped.append(elem) 559 560 # Return the list 561 return stripped 562 563 def misracstyle(self): 564 return False; 565 566 def misracppstyle(self): 567 return False; 568 569 def buildEnumCDecl(self, expand, groupinfo, groupName): 570 """Generate the C declaration for an enum""" 571 if self.genOpts is None: 572 raise MissingGeneratorOptionsError() 573 if self.genOpts.conventions is None: 574 raise MissingGeneratorOptionsConventionsError() 575 576 groupElem = groupinfo.elem 577 578 # Determine the required bit width for the enum group. 579 # 32 is the default, which generates C enum types for the values. 580 bitwidth = 32 581 582 # If the constFlagBits preference is set, 64 is the default for bitmasks 583 if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': 584 bitwidth = 64 585 586 # Check for an explicitly defined bitwidth, which will override any defaults. 587 if groupElem.get('bitwidth'): 588 try: 589 bitwidth = int(groupElem.get('bitwidth')) 590 except ValueError as ve: 591 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n') 592 exit(1) 593 594 usebitmask = False 595 usedefine = False 596 597 # Bitmask flags can be generated as either "static const uint{32,64}_t" values, 598 # or as 32-bit C enums. 64-bit types must use uint64_t values. 599 if groupElem.get('type') == 'bitmask': 600 if bitwidth > 32 or self.misracppstyle(): 601 usebitmask = True 602 if self.misracstyle(): 603 usedefine = True 604 605 if usedefine or usebitmask: 606 # Validate the bitwidth and generate values appropriately 607 if bitwidth > 64: 608 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n') 609 exit(1) 610 else: 611 return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine) 612 else: 613 # Validate the bitwidth and generate values appropriately 614 if bitwidth > 32: 615 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n') 616 exit(1) 617 else: 618 return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) 619 620 def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine): 621 """Generate the C declaration for an "enum" that is actually a 622 set of flag bits""" 623 groupElem = groupinfo.elem 624 flagTypeName = groupElem.get('name') 625 626 # Prefix 627 body = "// Flag bits for " + flagTypeName + "\n" 628 629 if bitwidth == 64: 630 body += "typedef VkFlags64 %s;\n" % flagTypeName; 631 else: 632 body += "typedef VkFlags %s;\n" % flagTypeName; 633 634 # Maximum allowable value for a flag (unsigned 64-bit integer) 635 maxValidValue = 2**(64) - 1 636 minValidValue = 0 637 638 # Get a list of nested 'enum' tags. 639 enums = groupElem.findall('enum') 640 641 # Check for and report duplicates, and return a list with them 642 # removed. 643 enums = self.checkDuplicateEnums(enums) 644 645 # Accumulate non-numeric enumerant values separately and append 646 # them following the numeric values, to allow for aliases. 647 # NOTE: this does not do a topological sort yet, so aliases of 648 # aliases can still get in the wrong order. 649 aliasText = '' 650 651 # Loop over the nested 'enum' tags. 652 for elem in enums: 653 # Convert the value to an integer and use that to track min/max. 654 # Values of form -(number) are accepted but nothing more complex. 655 # Should catch exceptions here for more complex constructs. Not yet. 656 (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True) 657 name = elem.get('name') 658 659 # Range check for the enum value 660 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 661 self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n') 662 exit(1) 663 664 decl = self.genRequirements(name, mustBeFound = False) 665 666 if self.isEnumRequired(elem): 667 protect = elem.get('protect') 668 if protect is not None: 669 body += '#ifdef {}\n'.format(protect) 670 671 if usedefine: 672 decl += "#define {} {}\n".format(name, strVal) 673 elif self.misracppstyle(): 674 decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal) 675 else: 676 # Some C compilers only allow initializing a 'static const' variable with a literal value. 677 # So initializing an alias from another 'static const' value would fail to compile. 678 # Work around this by chasing the aliases to get the actual value. 679 while numVal is None: 680 alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']") 681 if alias is not None: 682 (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True) 683 else: 684 self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name)) 685 decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal) 686 687 if numVal is not None: 688 body += decl 689 else: 690 aliasText += decl 691 692 if protect is not None: 693 body += '#endif\n' 694 695 # Now append the non-numeric enumerant values 696 body += aliasText 697 698 # Postfix 699 700 return ("bitmask", body) 701 702 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): 703 """Generate the C declaration for an enumerated type""" 704 groupElem = groupinfo.elem 705 706 # Break the group name into prefix and suffix portions for range 707 # enum generation 708 expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper() 709 expandPrefix = expandName 710 expandSuffix = '' 711 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName) 712 if expandSuffixMatch: 713 expandSuffix = '_' + expandSuffixMatch.group() 714 # Strip off the suffix from the prefix 715 expandPrefix = expandName.rsplit(expandSuffix, 1)[0] 716 717 # Prefix 718 body = ["typedef enum %s {" % groupName] 719 720 # @@ Should use the type="bitmask" attribute instead 721 isEnum = ('FLAG_BITS' not in expandPrefix) 722 723 # Allowable range for a C enum - which is that of a signed 32-bit integer 724 maxValidValue = 2**(32 - 1) - 1 725 minValidValue = (maxValidValue * -1) - 1 726 727 # Get a list of nested 'enum' tags. 728 enums = groupElem.findall('enum') 729 730 # Check for and report duplicates, and return a list with them 731 # removed. 732 enums = self.checkDuplicateEnums(enums) 733 734 # Loop over the nested 'enum' tags. Keep track of the minimum and 735 # maximum numeric values, if they can be determined; but only for 736 # core API enumerants, not extension enumerants. This is inferred 737 # by looking for 'extends' attributes. 738 minName = None 739 740 # Accumulate non-numeric enumerant values separately and append 741 # them following the numeric values, to allow for aliases. 742 # NOTE: this does not do a topological sort yet, so aliases of 743 # aliases can still get in the wrong order. 744 aliasText = [] 745 746 maxName = None 747 minValue = None 748 maxValue = None 749 for elem in enums: 750 # Convert the value to an integer and use that to track min/max. 751 # Values of form -(number) are accepted but nothing more complex. 752 # Should catch exceptions here for more complex constructs. Not yet. 753 (numVal, strVal) = self.enumToValue(elem, True) 754 name = elem.get('name') 755 756 # Extension enumerants are only included if they are required 757 if self.isEnumRequired(elem): 758 decl = '' 759 760 protect = elem.get('protect') 761 if protect is not None: 762 decl += '#ifdef {}\n'.format(protect) 763 764 # Indent requirements comment, if there is one 765 requirements = self.genRequirements(name, mustBeFound = False) 766 if requirements != '': 767 requirements = ' ' + requirements 768 decl += requirements 769 decl += ' {} = {},'.format(name, strVal) 770 771 if protect is not None: 772 decl += '\n#endif' 773 774 if numVal is not None: 775 body.append(decl) 776 else: 777 aliasText.append(decl) 778 779 # Range check for the enum value 780 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 781 self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n') 782 exit(1) 783 784 # Do not track min/max for non-numbers (numVal is None) 785 if isEnum and numVal is not None and elem.get('extends') is None: 786 if minName is None: 787 minName = maxName = name 788 minValue = maxValue = numVal 789 elif minValue is None or numVal < minValue: 790 minName = name 791 minValue = numVal 792 elif maxValue is None or numVal > maxValue: 793 maxName = name 794 maxValue = numVal 795 796 # Now append the non-numeric enumerant values 797 body.extend(aliasText) 798 799 # Generate min/max value tokens - legacy use case. 800 if isEnum and expand: 801 body.extend((f' {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},', 802 f' {expandPrefix}_END_RANGE{expandSuffix} = {maxName},', 803 f' {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),')) 804 805 # Generate a range-padding value to ensure the enum is 32 bits, but 806 # only in code generators, so it does not appear in documentation 807 if (self.genOpts.codeGenerator or 808 self.conventions.generate_max_enum_in_docs): 809 body.append(f' {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF') 810 811 # Postfix 812 body.append("} %s;" % groupName) 813 814 # Determine appropriate section for this declaration 815 if groupElem.get('type') == 'bitmask': 816 section = 'bitmask' 817 else: 818 section = 'group' 819 820 return (section, '\n'.join(body)) 821 822 def buildConstantCDecl(self, enuminfo, name, alias): 823 """Generate the C declaration for a constant (a single <enum> 824 value). 825 826 <enum> tags may specify their values in several ways, but are 827 usually just integers or floating-point numbers.""" 828 829 (_, strVal) = self.enumToValue(enuminfo.elem, False) 830 831 if self.misracppstyle() and enuminfo.elem.get('type') and not alias: 832 # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U); 833 # This appeases MISRA "underlying type" rules. 834 typeStr = enuminfo.elem.get('type'); 835 invert = '~' in strVal 836 number = strVal.strip("()~UL") 837 if typeStr != "float": 838 number += 'U' 839 strVal = "~" if invert else "" 840 strVal += "static_cast<" + typeStr + ">(" + number + ")" 841 body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};' 842 elif enuminfo.elem.get('type') and not alias: 843 # Generate e.g.: #define x (~0ULL) 844 typeStr = enuminfo.elem.get('type'); 845 invert = '~' in strVal 846 paren = '(' in strVal 847 number = strVal.strip("()~UL") 848 if typeStr != "float": 849 if typeStr == "uint64_t": 850 number += 'ULL' 851 else: 852 number += 'U' 853 strVal = "~" if invert else "" 854 strVal += number 855 if paren: 856 strVal = "(" + strVal + ")"; 857 body = '#define ' + name.ljust(33) + ' ' + strVal; 858 else: 859 body = '#define ' + name.ljust(33) + ' ' + strVal 860 861 return body 862 863 def makeDir(self, path): 864 """Create a directory, if not already done. 865 866 Generally called from derived generators creating hierarchies.""" 867 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') 868 if path not in self.madeDirs: 869 # This can get race conditions with multiple writers, see 870 # https://stackoverflow.com/questions/273192/ 871 if not os.path.exists(path): 872 os.makedirs(path) 873 self.madeDirs[path] = None 874 875 def beginFile(self, genOpts): 876 """Start a new interface file 877 878 - genOpts - GeneratorOptions controlling what is generated and how""" 879 880 self.genOpts = genOpts 881 if self.genOpts is None: 882 raise MissingGeneratorOptionsError() 883 if self.genOpts.conventions is None: 884 raise MissingGeneratorOptionsConventionsError() 885 self.should_insert_may_alias_macro = \ 886 self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) 887 self.file_suffix = self.genOpts.conventions.file_suffix 888 889 # Try to import the API dictionary, apimap.py, if it exists. Nothing 890 # in apimap.py cannot be extracted directly from the XML, and in the 891 # future we should do that. 892 if self.genOpts.genpath is not None: 893 try: 894 sys.path.insert(0, self.genOpts.genpath) 895 import apimap 896 self.apidict = apimap 897 except ImportError: 898 self.apidict = None 899 900 self.conventions = genOpts.conventions 901 902 # Open a temporary file for accumulating output. 903 if self.genOpts.filename is not None: 904 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) 905 else: 906 self.outFile = sys.stdout 907 908 def endFile(self): 909 if self.errFile: 910 self.errFile.flush() 911 if self.warnFile: 912 self.warnFile.flush() 913 if self.diagFile: 914 self.diagFile.flush() 915 if self.outFile: 916 self.outFile.flush() 917 if self.outFile != sys.stdout and self.outFile != sys.stderr: 918 self.outFile.close() 919 920 if self.genOpts is None: 921 raise MissingGeneratorOptionsError() 922 923 # On successfully generating output, move the temporary file to the 924 # target file. 925 if self.genOpts.filename is not None: 926 if sys.platform == 'win32': 927 directory = Path(self.genOpts.directory) 928 if not Path.exists(directory): 929 os.makedirs(directory) 930 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) 931 os.remove(self.outFile.name) 932 self.genOpts = None 933 934 def beginFeature(self, interface, emit): 935 """Write interface for a feature and tag generated features as having been done. 936 937 - interface - element for the `<version>` / `<extension>` to generate 938 - emit - actually write to the header only when True""" 939 self.emit = emit 940 self.featureName = interface.get('name') 941 self.featureType = interface.get('type') 942 # If there is an additional 'protect' attribute in the feature, save it 943 self.featureExtraProtect = interface.get('protect') 944 945 def endFeature(self): 946 """Finish an interface file, closing it when done. 947 948 Derived classes responsible for emitting feature""" 949 self.featureName = None 950 self.featureType = None 951 self.featureExtraProtect = None 952 953 def genRequirements(self, name, mustBeFound = True): 954 """Generate text showing what core versions and extensions introduce 955 an API. This exists in the base Generator class because it is used by 956 the shared enumerant-generating interfaces (buildEnumCDecl, etc.). 957 Here it returns an empty string for most generators, but can be 958 overridden by e.g. DocGenerator. 959 960 - name - name of the API 961 - mustBeFound - If True, when requirements for 'name' cannot be 962 determined, a warning comment is generated. 963 """ 964 965 return '' 966 967 def validateFeature(self, featureType, featureName): 968 """Validate we are generating something only inside a `<feature>` tag""" 969 if self.featureName is None: 970 raise UserWarning('Attempt to generate', featureType, 971 featureName, 'when not in feature') 972 973 def genType(self, typeinfo, name, alias): 974 """Generate interface for a type 975 976 - typeinfo - TypeInfo for a type 977 978 Extend to generate as desired in your derived class.""" 979 self.validateFeature('type', name) 980 981 def genStruct(self, typeinfo, typeName, alias): 982 """Generate interface for a C "struct" type. 983 984 - typeinfo - TypeInfo for a type interpreted as a struct 985 986 Extend to generate as desired in your derived class.""" 987 self.validateFeature('struct', typeName) 988 989 # The mixed-mode <member> tags may contain no-op <comment> tags. 990 # It is convenient to remove them here where all output generators 991 # will benefit. 992 for member in typeinfo.elem.findall('.//member'): 993 for comment in member.findall('comment'): 994 member.remove(comment) 995 996 def genGroup(self, groupinfo, groupName, alias): 997 """Generate interface for a group of enums (C "enum") 998 999 - groupinfo - GroupInfo for a group. 1000 1001 Extend to generate as desired in your derived class.""" 1002 1003 self.validateFeature('group', groupName) 1004 1005 def genEnum(self, enuminfo, typeName, alias): 1006 """Generate interface for an enum (constant). 1007 1008 - enuminfo - EnumInfo for an enum 1009 - name - enum name 1010 1011 Extend to generate as desired in your derived class.""" 1012 self.validateFeature('enum', typeName) 1013 1014 def genCmd(self, cmd, cmdinfo, alias): 1015 """Generate interface for a command. 1016 1017 - cmdinfo - CmdInfo for a command 1018 1019 Extend to generate as desired in your derived class.""" 1020 self.validateFeature('command', cmdinfo) 1021 1022 def genSpirv(self, spirv, spirvinfo, alias): 1023 """Generate interface for a spirv element. 1024 1025 - spirvinfo - SpirvInfo for a command 1026 1027 Extend to generate as desired in your derived class.""" 1028 return 1029 1030 def genFormat(self, format, formatinfo, alias): 1031 """Generate interface for a format element. 1032 1033 - formatinfo - FormatInfo 1034 1035 Extend to generate as desired in your derived class.""" 1036 return 1037 1038 def genSyncStage(self, stageinfo): 1039 """Generate interface for a sync stage element. 1040 1041 - stageinfo - SyncStageInfo 1042 1043 Extend to generate as desired in your derived class.""" 1044 return 1045 1046 def genSyncAccess(self, accessinfo): 1047 """Generate interface for a sync stage element. 1048 1049 - accessinfo - AccessInfo 1050 1051 Extend to generate as desired in your derived class.""" 1052 return 1053 1054 def genSyncPipeline(self, pipelineinfo): 1055 """Generate interface for a sync stage element. 1056 1057 - pipelineinfo - SyncPipelineInfo 1058 1059 Extend to generate as desired in your derived class.""" 1060 return 1061 1062 def makeProtoName(self, name, tail): 1063 """Turn a `<proto>` `<name>` into C-language prototype 1064 and typedef declarations for that name. 1065 1066 - name - contents of `<name>` tag 1067 - tail - whatever text follows that tag in the Element""" 1068 if self.genOpts is None: 1069 raise MissingGeneratorOptionsError() 1070 return self.genOpts.apientry + name + tail 1071 1072 def makeTypedefName(self, name, tail): 1073 """Make the function-pointer typedef name for a command.""" 1074 if self.genOpts is None: 1075 raise MissingGeneratorOptionsError() 1076 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 1077 1078 def makeCParamDecl(self, param, aligncol): 1079 """Return a string which is an indented, formatted 1080 declaration for a `<param>` or `<member>` block (e.g. function parameter 1081 or structure/union member). 1082 1083 - param - Element (`<param>` or `<member>`) to format 1084 - aligncol - if non-zero, attempt to align the nested `<name>` element 1085 at this column""" 1086 if self.genOpts is None: 1087 raise MissingGeneratorOptionsError() 1088 if self.genOpts.conventions is None: 1089 raise MissingGeneratorOptionsConventionsError() 1090 indent = ' ' 1091 paramdecl = indent 1092 prefix = noneStr(param.text) 1093 1094 for elem in param: 1095 text = noneStr(elem.text) 1096 tail = noneStr(elem.tail) 1097 1098 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 1099 # OpenXR-specific macro insertion - but not in apiinc for the spec 1100 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 1101 if elem.tag == 'name' and aligncol > 0: 1102 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 1103 # Align at specified column, if possible 1104 paramdecl = paramdecl.rstrip() 1105 oldLen = len(paramdecl) 1106 # This works around a problem where very long type names - 1107 # longer than the alignment column - would run into the tail 1108 # text. 1109 paramdecl = paramdecl.ljust(aligncol - 1) + ' ' 1110 newLen = len(paramdecl) 1111 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 1112 1113 if (self.misracppstyle() and prefix.find('const ') != -1): 1114 # Change pointer type order from e.g. "const void *" to "void const *". 1115 # If the string starts with 'const', reorder it to be after the first type. 1116 paramdecl += prefix.replace('const ', '') + text + ' const' + tail 1117 else: 1118 paramdecl += prefix + text + tail 1119 1120 # Clear prefix for subsequent iterations 1121 prefix = '' 1122 1123 paramdecl = paramdecl + prefix 1124 1125 if aligncol == 0: 1126 # Squeeze out multiple spaces other than the indentation 1127 paramdecl = indent + ' '.join(paramdecl.split()) 1128 return paramdecl 1129 1130 def getCParamTypeLength(self, param): 1131 """Return the length of the type field is an indented, formatted 1132 declaration for a `<param>` or `<member>` block (e.g. function parameter 1133 or structure/union member). 1134 1135 - param - Element (`<param>` or `<member>`) to identify""" 1136 if self.genOpts is None: 1137 raise MissingGeneratorOptionsError() 1138 if self.genOpts.conventions is None: 1139 raise MissingGeneratorOptionsConventionsError() 1140 1141 # Allow for missing <name> tag 1142 newLen = 0 1143 paramdecl = ' ' + noneStr(param.text) 1144 for elem in param: 1145 text = noneStr(elem.text) 1146 tail = noneStr(elem.tail) 1147 1148 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 1149 # OpenXR-specific macro insertion 1150 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 1151 if elem.tag == 'name': 1152 # Align at specified column, if possible 1153 newLen = len(paramdecl.rstrip()) 1154 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 1155 paramdecl += text + tail 1156 1157 return newLen 1158 1159 def getMaxCParamTypeLength(self, info): 1160 """Return the length of the longest type field for a member/parameter. 1161 1162 - info - TypeInfo or CommandInfo. 1163 """ 1164 lengths = (self.getCParamTypeLength(member) 1165 for member in info.getMembers()) 1166 return max(lengths) 1167 1168 def getHandleParent(self, typename): 1169 """Get the parent of a handle object.""" 1170 if self.registry is None: 1171 raise MissingRegistryError() 1172 1173 info = self.registry.typedict.get(typename) 1174 if info is None: 1175 return None 1176 1177 elem = info.elem 1178 if elem is not None: 1179 return elem.get('parent') 1180 1181 return None 1182 1183 def iterateHandleAncestors(self, typename): 1184 """Iterate through the ancestors of a handle type.""" 1185 current = self.getHandleParent(typename) 1186 while current is not None: 1187 yield current 1188 current = self.getHandleParent(current) 1189 1190 def getHandleAncestors(self, typename): 1191 """Get the ancestors of a handle object.""" 1192 return list(self.iterateHandleAncestors(typename)) 1193 1194 def getTypeCategory(self, typename): 1195 """Get the category of a type.""" 1196 if self.registry is None: 1197 raise MissingRegistryError() 1198 1199 info = self.registry.typedict.get(typename) 1200 if info is None: 1201 return None 1202 1203 elem = info.elem 1204 if elem is not None: 1205 return elem.get('category') 1206 return None 1207 1208 def isStructAlwaysValid(self, structname): 1209 """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance).""" 1210 # A conventions object is required for this call. 1211 if not self.conventions: 1212 raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") 1213 if self.registry is None: 1214 raise MissingRegistryError() 1215 1216 if self.conventions.type_always_valid(structname): 1217 return True 1218 1219 category = self.getTypeCategory(structname) 1220 if self.conventions.category_requires_validation(category): 1221 return False 1222 1223 info = self.registry.typedict.get(structname) 1224 if info is None: 1225 self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict') 1226 1227 members = info.getMembers() 1228 1229 for member in members: 1230 member_name = getElemName(member) 1231 if member_name in (self.conventions.structtype_member_name, 1232 self.conventions.nextpointer_member_name): 1233 return False 1234 1235 if member.get('noautovalidity'): 1236 return False 1237 1238 member_type = getElemType(member) 1239 1240 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): 1241 return False 1242 1243 if self.conventions.type_always_valid(member_type): 1244 continue 1245 1246 member_category = self.getTypeCategory(member_type) 1247 1248 if self.conventions.category_requires_validation(member_category): 1249 return False 1250 1251 if member_category in ('struct', 'union'): 1252 if self.isStructAlwaysValid(member_type) is False: 1253 return False 1254 1255 return True 1256 1257 def paramIsArray(self, param): 1258 """Check if the parameter passed in is a pointer to an array. 1259 1260 param the XML information for the param 1261 """ 1262 return param.get('len') is not None 1263 1264 def paramIsPointer(self, param): 1265 """Check if the parameter passed in is a pointer. 1266 1267 param the XML information for the param 1268 """ 1269 tail = param.find('type').tail 1270 return tail is not None and '*' in tail 1271 1272 def isEnumRequired(self, elem): 1273 """Return True if this `<enum>` element is 1274 required, False otherwise 1275 1276 - elem - `<enum>` element to test""" 1277 required = elem.get('required') is not None 1278 self.logMsg('diag', 'isEnumRequired:', elem.get('name'), 1279 '->', required) 1280 return required 1281 1282 # @@@ This code is overridden by equivalent code now run in 1283 # @@@ Registry.generateFeature 1284 1285 required = False 1286 1287 extname = elem.get('extname') 1288 if extname is not None: 1289 # 'supported' attribute was injected when the <enum> element was 1290 # moved into the <enums> group in Registry.parseTree() 1291 if self.genOpts.defaultExtensions == elem.get('supported'): 1292 required = True 1293 elif re.match(self.genOpts.addExtensions, extname) is not None: 1294 required = True 1295 elif elem.get('version') is not None: 1296 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 1297 else: 1298 required = True 1299 1300 return required 1301 1302 def makeCDecls(self, cmd): 1303 """Return C prototype and function pointer typedef for a 1304 `<command>` Element, as a two-element list of strings. 1305 1306 - cmd - Element containing a `<command>` tag""" 1307 if self.genOpts is None: 1308 raise MissingGeneratorOptionsError() 1309 proto = cmd.find('proto') 1310 params = cmd.findall('param') 1311 # Begin accumulating prototype and typedef strings 1312 pdecl = self.genOpts.apicall 1313 tdecl = 'typedef ' 1314 1315 # Insert the function return type/name. 1316 # For prototypes, add APIENTRY macro before the name 1317 # For typedefs, add (APIENTRY *<name>) around the name and 1318 # use the PFN_cmdnameproc naming convention. 1319 # Done by walking the tree for <proto> element by element. 1320 # etree has elem.text followed by (elem[i], elem[i].tail) 1321 # for each child element and any following text 1322 # Leading text 1323 pdecl += noneStr(proto.text) 1324 tdecl += noneStr(proto.text) 1325 # For each child element, if it is a <name> wrap in appropriate 1326 # declaration. Otherwise append its contents and tail contents. 1327 for elem in proto: 1328 text = noneStr(elem.text) 1329 tail = noneStr(elem.tail) 1330 if elem.tag == 'name': 1331 pdecl += self.makeProtoName(text, tail) 1332 tdecl += self.makeTypedefName(text, tail) 1333 else: 1334 pdecl += text + tail 1335 tdecl += text + tail 1336 1337 if self.genOpts.alignFuncParam == 0: 1338 # Squeeze out multiple spaces - there is no indentation 1339 pdecl = ' '.join(pdecl.split()) 1340 tdecl = ' '.join(tdecl.split()) 1341 1342 # Now add the parameter declaration list, which is identical 1343 # for prototypes and typedefs. Concatenate all the text from 1344 # a <param> node without the tags. No tree walking required 1345 # since all tags are ignored. 1346 # Uses: self.indentFuncProto 1347 # self.indentFuncPointer 1348 # self.alignFuncParam 1349 n = len(params) 1350 # Indented parameters 1351 if n > 0: 1352 indentdecl = '(\n' 1353 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 1354 for p in params) 1355 indentdecl += ');' 1356 else: 1357 indentdecl = '(void);' 1358 # Non-indented parameters 1359 paramdecl = '(' 1360 if n > 0: 1361 paramnames = [] 1362 if self.misracppstyle(): 1363 for p in params: 1364 param = '' 1365 firstIter = True; 1366 for t in p.itertext(): 1367 if (firstIter): 1368 prefix = t 1369 firstIter = False 1370 else: 1371 # Change pointer type order from e.g. "const void *" to "void const *". 1372 # If the string starts with 'const', reorder it to be after the first type. 1373 if (prefix.find('const ') != -1): 1374 param += prefix.replace('const ', '') + t + ' const ' 1375 else: 1376 param += prefix + t 1377 # Clear prefix for subsequent iterations 1378 prefix = '' 1379 paramnames.append(param); 1380 else: 1381 paramnames = (''.join(t for t in p.itertext()) 1382 for p in params) 1383 paramdecl += ', '.join(paramnames) 1384 else: 1385 paramdecl += 'void' 1386 paramdecl += ");" 1387 return [pdecl + indentdecl, tdecl + paramdecl] 1388 1389 def newline(self): 1390 """Print a newline to the output file (utility function)""" 1391 write('', file=self.outFile) 1392 1393 def setRegistry(self, registry): 1394 self.registry = registry 1395