mirror of
https://github.com/status-im/Vulkan-Docs.git
synced 2025-02-26 13:05:13 +00:00
223 lines
7.6 KiB
Python
223 lines
7.6 KiB
Python
|
#!/usr/bin/python3 -i
|
||
|
#
|
||
|
# Copyright (c) 2013-2019 The Khronos Group Inc.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
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,)
|
||
|
|
||
|
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)
|