217 lines
7.3 KiB
Python
217 lines
7.3 KiB
Python
#!/usr/bin/python3 -i
|
|
#
|
|
# Copyright 2013-2021 The Khronos Group Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import re
|
|
|
|
|
|
_A_VS_AN_RE = re.compile(r' a ([a-z]+:)?([aAeEiIoOxX]\w+\b)(?!:)')
|
|
|
|
_STARTS_WITH_MACRO_RE = re.compile(r'^[a-z]+:.*')
|
|
|
|
|
|
def _checkAnchorComponents(anchor):
|
|
"""Raise an exception if any component of a VUID anchor name is illegal."""
|
|
if anchor:
|
|
# Any other invalid things in an anchor name should be detected here.
|
|
if any((' ' in anchor_part for anchor_part in anchor)):
|
|
raise RuntimeError("Illegal component of a VUID anchor name!")
|
|
|
|
|
|
def _fix_a_vs_an(s):
|
|
"""Fix usage (often generated) of the indefinite article 'a' when 'an' is appropriate.
|
|
|
|
Explicitly excludes the markup macros."""
|
|
return _A_VS_AN_RE.sub(r' an \1\2', s)
|
|
|
|
|
|
class ValidityCollection:
|
|
"""Combines validity for a single entity."""
|
|
|
|
def __init__(self, entity_name=None, conventions=None, strict=True):
|
|
self.entity_name = entity_name
|
|
self.conventions = conventions
|
|
self.lines = []
|
|
self.strict = strict
|
|
|
|
def possiblyAddExtensionRequirement(self, extension_name, entity_preface):
|
|
"""Add an extension-related validity statement if required.
|
|
|
|
entity_preface is a string that goes between "must be enabled prior to "
|
|
and the name of the entity, and normally ends in a macro.
|
|
For instance, might be "calling flink:" for a function.
|
|
"""
|
|
if extension_name and not extension_name.startswith(self.conventions.api_version_prefix):
|
|
msg = 'The {} extension must: be enabled prior to {}{}'.format(
|
|
self.conventions.formatExtension(extension_name), entity_preface, self.entity_name)
|
|
self.addValidityEntry(msg, anchor=('extension', 'notenabled'))
|
|
|
|
def addValidityEntry(self, msg, anchor=None):
|
|
"""Add a validity entry, optionally with a VUID anchor.
|
|
|
|
If any trailing arguments are supplied,
|
|
an anchor is generated by concatenating them with dashes
|
|
at the end of the VUID anchor name.
|
|
"""
|
|
if not msg:
|
|
raise RuntimeError("Tried to add a blank validity line!")
|
|
parts = ['*']
|
|
_checkAnchorComponents(anchor)
|
|
if anchor:
|
|
if not self.entity_name:
|
|
raise RuntimeError('Cannot add a validity entry with an anchor to a collection that does not know its entity name.')
|
|
parts.append('[[{}]]'.format(
|
|
'-'.join(['VUID', self.entity_name] + list(anchor))))
|
|
parts.append(msg)
|
|
combined = _fix_a_vs_an(' '.join(parts))
|
|
if combined in self.lines:
|
|
raise RuntimeError("Duplicate validity added!")
|
|
self.lines.append(combined)
|
|
|
|
def addText(self, msg):
|
|
"""Add already formatted validity text."""
|
|
if self.strict:
|
|
raise RuntimeError('addText called when collection in strict mode')
|
|
if not msg:
|
|
return
|
|
msg = msg.rstrip()
|
|
if not msg:
|
|
return
|
|
self.lines.append(msg)
|
|
|
|
def _extend(self, lines):
|
|
lines = list(lines)
|
|
dupes = set(lines).intersection(self.lines)
|
|
if dupes:
|
|
raise RuntimeError("The two sets contain some shared entries! " + str(dupes))
|
|
self.lines.extend(lines)
|
|
|
|
def __iadd__(self, other):
|
|
"""Perform += with a string, iterable, or ValidityCollection."""
|
|
if other is None:
|
|
pass
|
|
elif isinstance(other, str):
|
|
if self.strict:
|
|
raise RuntimeError(
|
|
'Collection += a string when collection in strict mode')
|
|
if not other:
|
|
# empty string
|
|
pass
|
|
elif other.startswith('*'):
|
|
# Handle already-formatted
|
|
self.addText(other)
|
|
else:
|
|
# Do the formatting ourselves.
|
|
self.addValidityEntry(other)
|
|
elif isinstance(other, ValidityEntry):
|
|
if other:
|
|
if other.verbose:
|
|
print(self.entity_name, 'Appending', str(other))
|
|
self.addValidityEntry(str(other), anchor=other.anchor)
|
|
elif isinstance(other, ValidityCollection):
|
|
if not self.entity_name == other.entity_name:
|
|
raise RuntimeError(
|
|
"Trying to combine two ValidityCollections for different entities!")
|
|
self._extend(other.lines)
|
|
else:
|
|
# Deal with other iterables.
|
|
self._extend(other)
|
|
|
|
return self
|
|
|
|
def __bool__(self):
|
|
"""Is the collection non-empty?"""
|
|
empty = not self.lines
|
|
return not empty
|
|
|
|
@property
|
|
def text(self):
|
|
"""Access validity statements as a single string or None."""
|
|
if not self.lines:
|
|
return None
|
|
return '\n'.join(self.lines) + '\n'
|
|
|
|
def __str__(self):
|
|
"""Access validity statements as a single string or empty string."""
|
|
if not self:
|
|
return ''
|
|
return self.text
|
|
|
|
def __repr__(self):
|
|
return '<ValidityCollection: {}>'.format(self.lines)
|
|
|
|
|
|
class ValidityEntry:
|
|
"""A single validity line in progress."""
|
|
|
|
def __init__(self, text=None, anchor=None):
|
|
"""Prepare to add a validity entry, optionally with a VUID anchor.
|
|
|
|
An anchor is generated by concatenating the elements of the anchor tuple with dashes
|
|
at the end of the VUID anchor name.
|
|
"""
|
|
_checkAnchorComponents(anchor)
|
|
if isinstance(anchor, str):
|
|
# anchor needs to be a tuple
|
|
anchor = (anchor,)
|
|
|
|
# VUID does not allow special chars except ":"
|
|
if anchor is not None:
|
|
anchor = [(anchor_value.replace('->', '::').replace('.', '::')) for anchor_value in anchor]
|
|
|
|
self.anchor = anchor
|
|
self.parts = []
|
|
self.verbose = False
|
|
if text:
|
|
self.append(text)
|
|
|
|
def append(self, part):
|
|
"""Append a part of a string.
|
|
|
|
If this is the first entry part and the part doesn't start
|
|
with a markup macro, the first character will be capitalized."""
|
|
if not self.parts and not _STARTS_WITH_MACRO_RE.match(part):
|
|
self.parts.append(part[:1].upper())
|
|
self.parts.append(part[1:])
|
|
else:
|
|
self.parts.append(part)
|
|
if self.verbose:
|
|
print('ValidityEntry', id(self), 'after append:', str(self))
|
|
|
|
def drop_end(self, n):
|
|
"""Remove up to n trailing characters from the string."""
|
|
temp = str(self)
|
|
n = min(len(temp), n)
|
|
self.parts = [temp[:-n]]
|
|
|
|
def __iadd__(self, other):
|
|
"""Perform += with a string,"""
|
|
self.append(other)
|
|
return self
|
|
|
|
def __bool__(self):
|
|
"""Return true if we have something more than just an anchor."""
|
|
empty = not self.parts
|
|
return not empty
|
|
|
|
def __str__(self):
|
|
"""Access validity statement as a single string or empty string."""
|
|
if not self:
|
|
raise RuntimeError("No parts added?")
|
|
return ''.join(self.parts).strip()
|
|
|
|
def __repr__(self):
|
|
parts = ['<ValidityEntry: ']
|
|
if self:
|
|
parts.append('"')
|
|
parts.append(str(self))
|
|
parts.append('"')
|
|
else:
|
|
parts.append('EMPTY')
|
|
if self.anchor:
|
|
parts.append(', anchor={}'.format('-'.join(self.anchor)))
|
|
parts.append('>')
|
|
return ''.join(parts)
|