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