1e5c31af7Sopenharmony_ci#!/usr/bin/python3 -i 2e5c31af7Sopenharmony_ci# 3e5c31af7Sopenharmony_ci# Copyright 2013-2024 The Khronos Group Inc. 4e5c31af7Sopenharmony_ci# 5e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0 6e5c31af7Sopenharmony_ci 7e5c31af7Sopenharmony_ciimport re 8e5c31af7Sopenharmony_ci 9e5c31af7Sopenharmony_ci 10e5c31af7Sopenharmony_ci_A_VS_AN_RE = re.compile(r' a ([a-z]+:)?([aAeEiIoOxX]\w+\b)(?!:)') 11e5c31af7Sopenharmony_ci 12e5c31af7Sopenharmony_ci_STARTS_WITH_MACRO_RE = re.compile(r'^[a-z]+:.*') 13e5c31af7Sopenharmony_ci 14e5c31af7Sopenharmony_ci_VUID_ANCHOR_RE = re.compile(r'\[\[VUID-.*\]\]') 15e5c31af7Sopenharmony_ci 16e5c31af7Sopenharmony_ci 17e5c31af7Sopenharmony_cidef _checkAnchorComponents(anchor): 18e5c31af7Sopenharmony_ci """Raise an exception if any component of a VUID anchor name is illegal.""" 19e5c31af7Sopenharmony_ci if anchor: 20e5c31af7Sopenharmony_ci # Any other invalid things in an anchor name should be detected here. 21e5c31af7Sopenharmony_ci if any((' ' in anchor_part for anchor_part in anchor)): 22e5c31af7Sopenharmony_ci raise RuntimeError("Illegal component of a VUID anchor name!") 23e5c31af7Sopenharmony_ci 24e5c31af7Sopenharmony_ci 25e5c31af7Sopenharmony_cidef _fix_a_vs_an(s): 26e5c31af7Sopenharmony_ci """Fix usage (often generated) of the indefinite article 'a' when 'an' is appropriate. 27e5c31af7Sopenharmony_ci 28e5c31af7Sopenharmony_ci Explicitly excludes the markup macros.""" 29e5c31af7Sopenharmony_ci return _A_VS_AN_RE.sub(r' an \1\2', s) 30e5c31af7Sopenharmony_ci 31e5c31af7Sopenharmony_ci 32e5c31af7Sopenharmony_ciclass ValidityCollection: 33e5c31af7Sopenharmony_ci """Combines validity for a single entity.""" 34e5c31af7Sopenharmony_ci 35e5c31af7Sopenharmony_ci def __init__(self, entity_name=None, conventions=None, strict=True, verbose=False): 36e5c31af7Sopenharmony_ci self.entity_name = entity_name 37e5c31af7Sopenharmony_ci self.conventions = conventions 38e5c31af7Sopenharmony_ci self.lines = [] 39e5c31af7Sopenharmony_ci self.strict = strict 40e5c31af7Sopenharmony_ci self.verbose = verbose 41e5c31af7Sopenharmony_ci 42e5c31af7Sopenharmony_ci def possiblyAddExtensionRequirement(self, extension_name, entity_preface): 43e5c31af7Sopenharmony_ci """Add an extension-related validity statement if required. 44e5c31af7Sopenharmony_ci 45e5c31af7Sopenharmony_ci entity_preface is a string that goes between "must be enabled prior to " 46e5c31af7Sopenharmony_ci and the name of the entity, and normally ends in a macro. 47e5c31af7Sopenharmony_ci For instance, might be "calling flink:" for a function. 48e5c31af7Sopenharmony_ci """ 49e5c31af7Sopenharmony_ci if extension_name and not extension_name.startswith(self.conventions.api_version_prefix): 50e5c31af7Sopenharmony_ci msg = 'The {} extension must: be enabled prior to {}{}'.format( 51e5c31af7Sopenharmony_ci self.conventions.formatExtension(extension_name), entity_preface, self.entity_name) 52e5c31af7Sopenharmony_ci self.addValidityEntry(msg, anchor=('extension', 'notenabled')) 53e5c31af7Sopenharmony_ci 54e5c31af7Sopenharmony_ci def addValidityEntry(self, msg, anchor=None): 55e5c31af7Sopenharmony_ci """Add a validity entry, optionally with a VUID anchor. 56e5c31af7Sopenharmony_ci 57e5c31af7Sopenharmony_ci If any trailing arguments are supplied, 58e5c31af7Sopenharmony_ci an anchor is generated by concatenating them with dashes 59e5c31af7Sopenharmony_ci at the end of the VUID anchor name. 60e5c31af7Sopenharmony_ci """ 61e5c31af7Sopenharmony_ci if not msg: 62e5c31af7Sopenharmony_ci raise RuntimeError("Tried to add a blank validity line!") 63e5c31af7Sopenharmony_ci parts = ['*'] 64e5c31af7Sopenharmony_ci _checkAnchorComponents(anchor) 65e5c31af7Sopenharmony_ci if anchor: 66e5c31af7Sopenharmony_ci if not self.entity_name: 67e5c31af7Sopenharmony_ci raise RuntimeError('Cannot add a validity entry with an anchor to a collection that does not know its entity name.') 68e5c31af7Sopenharmony_ci parts.append('[[{}]]'.format( 69e5c31af7Sopenharmony_ci '-'.join(['VUID', self.entity_name] + list(anchor)))) 70e5c31af7Sopenharmony_ci parts.append(msg) 71e5c31af7Sopenharmony_ci combined = _fix_a_vs_an(' '.join(parts)) 72e5c31af7Sopenharmony_ci if combined in self.lines: 73e5c31af7Sopenharmony_ci raise RuntimeError("Duplicate validity added!") 74e5c31af7Sopenharmony_ci self.lines.append(combined) 75e5c31af7Sopenharmony_ci 76e5c31af7Sopenharmony_ci def addText(self, msg): 77e5c31af7Sopenharmony_ci """Add already formatted validity text.""" 78e5c31af7Sopenharmony_ci if self.strict: 79e5c31af7Sopenharmony_ci raise RuntimeError('addText called when collection in strict mode') 80e5c31af7Sopenharmony_ci if not msg: 81e5c31af7Sopenharmony_ci return 82e5c31af7Sopenharmony_ci msg = msg.rstrip() 83e5c31af7Sopenharmony_ci if not msg: 84e5c31af7Sopenharmony_ci return 85e5c31af7Sopenharmony_ci self.lines.append(msg) 86e5c31af7Sopenharmony_ci 87e5c31af7Sopenharmony_ci def _extend(self, lines): 88e5c31af7Sopenharmony_ci lines = list(lines) 89e5c31af7Sopenharmony_ci dupes = set(lines).intersection(self.lines) 90e5c31af7Sopenharmony_ci if dupes: 91e5c31af7Sopenharmony_ci raise RuntimeError("The two sets contain some shared entries! " + str(dupes)) 92e5c31af7Sopenharmony_ci self.lines.extend(lines) 93e5c31af7Sopenharmony_ci 94e5c31af7Sopenharmony_ci def __iadd__(self, other): 95e5c31af7Sopenharmony_ci """Perform += with a string, iterable, or ValidityCollection.""" 96e5c31af7Sopenharmony_ci if other is None: 97e5c31af7Sopenharmony_ci pass 98e5c31af7Sopenharmony_ci elif isinstance(other, str): 99e5c31af7Sopenharmony_ci if self.strict: 100e5c31af7Sopenharmony_ci raise RuntimeError( 101e5c31af7Sopenharmony_ci 'Collection += a string when collection in strict mode') 102e5c31af7Sopenharmony_ci if not other: 103e5c31af7Sopenharmony_ci # empty string 104e5c31af7Sopenharmony_ci pass 105e5c31af7Sopenharmony_ci elif other.startswith('*'): 106e5c31af7Sopenharmony_ci # Handle already-formatted 107e5c31af7Sopenharmony_ci self.addText(other) 108e5c31af7Sopenharmony_ci else: 109e5c31af7Sopenharmony_ci # Do the formatting ourselves. 110e5c31af7Sopenharmony_ci self.addValidityEntry(other) 111e5c31af7Sopenharmony_ci elif isinstance(other, ValidityEntry): 112e5c31af7Sopenharmony_ci if other: 113e5c31af7Sopenharmony_ci if other.verbose: 114e5c31af7Sopenharmony_ci print(self.entity_name, 'Appending', str(other)) 115e5c31af7Sopenharmony_ci self.addValidityEntry(str(other), anchor=other.anchor) 116e5c31af7Sopenharmony_ci elif isinstance(other, ValidityCollection): 117e5c31af7Sopenharmony_ci if self.entity_name == other.entity_name: 118e5c31af7Sopenharmony_ci self._extend(other.lines) 119e5c31af7Sopenharmony_ci else: 120e5c31af7Sopenharmony_ci # Remove foreign anchors - this is presumably an alias 121e5c31af7Sopenharmony_ci if other.verbose: 122e5c31af7Sopenharmony_ci print(self.entity_name, 123e5c31af7Sopenharmony_ci 'merging with validity for', 124e5c31af7Sopenharmony_ci other.entity_name, 125e5c31af7Sopenharmony_ci 'so removing VUID anchor on incoming entries') 126e5c31af7Sopenharmony_ci self._extend(_VUID_ANCHOR_RE.sub('', s, 1) for s in other.lines) 127e5c31af7Sopenharmony_ci else: 128e5c31af7Sopenharmony_ci # Deal with other iterables. 129e5c31af7Sopenharmony_ci self._extend(other) 130e5c31af7Sopenharmony_ci 131e5c31af7Sopenharmony_ci return self 132e5c31af7Sopenharmony_ci 133e5c31af7Sopenharmony_ci def __bool__(self): 134e5c31af7Sopenharmony_ci """Is the collection non-empty?""" 135e5c31af7Sopenharmony_ci empty = not self.lines 136e5c31af7Sopenharmony_ci return not empty 137e5c31af7Sopenharmony_ci 138e5c31af7Sopenharmony_ci @property 139e5c31af7Sopenharmony_ci def text(self): 140e5c31af7Sopenharmony_ci """Access validity statements as a single string or None.""" 141e5c31af7Sopenharmony_ci if not self.lines: 142e5c31af7Sopenharmony_ci return None 143e5c31af7Sopenharmony_ci return '\n'.join(self.lines) + '\n' 144e5c31af7Sopenharmony_ci 145e5c31af7Sopenharmony_ci def __str__(self): 146e5c31af7Sopenharmony_ci """Access validity statements as a single string or empty string.""" 147e5c31af7Sopenharmony_ci if not self: 148e5c31af7Sopenharmony_ci return '' 149e5c31af7Sopenharmony_ci return self.text 150e5c31af7Sopenharmony_ci 151e5c31af7Sopenharmony_ci def __repr__(self): 152e5c31af7Sopenharmony_ci return '<ValidityCollection: {}>'.format(self.lines) 153e5c31af7Sopenharmony_ci 154e5c31af7Sopenharmony_ci 155e5c31af7Sopenharmony_ciclass ValidityEntry: 156e5c31af7Sopenharmony_ci """A single validity line in progress.""" 157e5c31af7Sopenharmony_ci 158e5c31af7Sopenharmony_ci def __init__(self, text=None, anchor=None): 159e5c31af7Sopenharmony_ci """Prepare to add a validity entry, optionally with a VUID anchor. 160e5c31af7Sopenharmony_ci 161e5c31af7Sopenharmony_ci An anchor is generated by concatenating the elements of the anchor tuple with dashes 162e5c31af7Sopenharmony_ci at the end of the VUID anchor name. 163e5c31af7Sopenharmony_ci """ 164e5c31af7Sopenharmony_ci _checkAnchorComponents(anchor) 165e5c31af7Sopenharmony_ci if isinstance(anchor, str): 166e5c31af7Sopenharmony_ci # anchor needs to be a tuple 167e5c31af7Sopenharmony_ci anchor = (anchor,) 168e5c31af7Sopenharmony_ci 169e5c31af7Sopenharmony_ci # VUID does not allow special chars except ":" 170e5c31af7Sopenharmony_ci if anchor is not None: 171e5c31af7Sopenharmony_ci anchor = [(anchor_value.replace('->', '::').replace('.', '::')) for anchor_value in anchor] 172e5c31af7Sopenharmony_ci 173e5c31af7Sopenharmony_ci self.anchor = anchor 174e5c31af7Sopenharmony_ci self.parts = [] 175e5c31af7Sopenharmony_ci self.verbose = False 176e5c31af7Sopenharmony_ci if text: 177e5c31af7Sopenharmony_ci self.append(text) 178e5c31af7Sopenharmony_ci 179e5c31af7Sopenharmony_ci def append(self, part): 180e5c31af7Sopenharmony_ci """Append a part of a string. 181e5c31af7Sopenharmony_ci 182e5c31af7Sopenharmony_ci If this is the first entry part and the part doesn't start 183e5c31af7Sopenharmony_ci with a markup macro, the first character will be capitalized.""" 184e5c31af7Sopenharmony_ci if not self.parts and not _STARTS_WITH_MACRO_RE.match(part): 185e5c31af7Sopenharmony_ci self.parts.append(part[:1].upper()) 186e5c31af7Sopenharmony_ci self.parts.append(part[1:]) 187e5c31af7Sopenharmony_ci else: 188e5c31af7Sopenharmony_ci self.parts.append(part) 189e5c31af7Sopenharmony_ci if self.verbose: 190e5c31af7Sopenharmony_ci print('ValidityEntry', id(self), 'after append:', str(self)) 191e5c31af7Sopenharmony_ci 192e5c31af7Sopenharmony_ci def drop_end(self, n): 193e5c31af7Sopenharmony_ci """Remove up to n trailing characters from the string.""" 194e5c31af7Sopenharmony_ci temp = str(self) 195e5c31af7Sopenharmony_ci n = min(len(temp), n) 196e5c31af7Sopenharmony_ci self.parts = [temp[:-n]] 197e5c31af7Sopenharmony_ci 198e5c31af7Sopenharmony_ci def __iadd__(self, other): 199e5c31af7Sopenharmony_ci """Perform += with a string,""" 200e5c31af7Sopenharmony_ci self.append(other) 201e5c31af7Sopenharmony_ci return self 202e5c31af7Sopenharmony_ci 203e5c31af7Sopenharmony_ci def __bool__(self): 204e5c31af7Sopenharmony_ci """Return true if we have something more than just an anchor.""" 205e5c31af7Sopenharmony_ci empty = not self.parts 206e5c31af7Sopenharmony_ci return not empty 207e5c31af7Sopenharmony_ci 208e5c31af7Sopenharmony_ci def __str__(self): 209e5c31af7Sopenharmony_ci """Access validity statement as a single string or empty string.""" 210e5c31af7Sopenharmony_ci if not self: 211e5c31af7Sopenharmony_ci raise RuntimeError("No parts added?") 212e5c31af7Sopenharmony_ci return ''.join(self.parts).strip() 213e5c31af7Sopenharmony_ci 214e5c31af7Sopenharmony_ci def __repr__(self): 215e5c31af7Sopenharmony_ci parts = ['<ValidityEntry: '] 216e5c31af7Sopenharmony_ci if self: 217e5c31af7Sopenharmony_ci parts.append('"') 218e5c31af7Sopenharmony_ci parts.append(str(self)) 219e5c31af7Sopenharmony_ci parts.append('"') 220e5c31af7Sopenharmony_ci else: 221e5c31af7Sopenharmony_ci parts.append('EMPTY') 222e5c31af7Sopenharmony_ci if self.anchor: 223e5c31af7Sopenharmony_ci parts.append(', anchor={}'.format('-'.join(self.anchor))) 224e5c31af7Sopenharmony_ci parts.append('>') 225e5c31af7Sopenharmony_ci return ''.join(parts) 226