1e5c31af7Sopenharmony_ci# Copyright 2023-2024 The Khronos Group Inc.
2e5c31af7Sopenharmony_ci#
3e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
4e5c31af7Sopenharmony_ci
5e5c31af7Sopenharmony_ci"""Utilities for automatic transformation of spec sources.  Most of the logic
6e5c31af7Sopenharmony_cihas to do with detecting asciidoc markup or block types that should not be
7e5c31af7Sopenharmony_citransformed (tables, code) and ignoring them.  It is very likely there are many
8e5c31af7Sopenharmony_ciasciidoc constructs not yet accounted for in the script, our usage of asciidoc
9e5c31af7Sopenharmony_cimarkup is intentionally somewhat limited.
10e5c31af7Sopenharmony_ci"""
11e5c31af7Sopenharmony_ci
12e5c31af7Sopenharmony_ciimport re
13e5c31af7Sopenharmony_ciimport sys
14e5c31af7Sopenharmony_cifrom reflib import logDiag, logWarn
15e5c31af7Sopenharmony_ci
16e5c31af7Sopenharmony_ci# Vulkan-specific - will consolidate into scripts/ like OpenXR soon
17e5c31af7Sopenharmony_cisys.path.insert(0, 'xml')
18e5c31af7Sopenharmony_ci
19e5c31af7Sopenharmony_cifrom apiconventions import APIConventions
20e5c31af7Sopenharmony_ciconventions = APIConventions()
21e5c31af7Sopenharmony_ci
22e5c31af7Sopenharmony_ci# Start of an asciidoctor conditional
23e5c31af7Sopenharmony_ci#   ifdef::
24e5c31af7Sopenharmony_ci#   ifndef::
25e5c31af7Sopenharmony_ciconditionalStart = re.compile(r'^(ifdef|ifndef)::')
26e5c31af7Sopenharmony_ci
27e5c31af7Sopenharmony_ci# Markup that always ends a paragraph
28e5c31af7Sopenharmony_ci#   empty line or whitespace
29e5c31af7Sopenharmony_ci#   [block options]
30e5c31af7Sopenharmony_ci#   [[anchor]]
31e5c31af7Sopenharmony_ci#   //                  comment
32e5c31af7Sopenharmony_ci#   <<<<                page break
33e5c31af7Sopenharmony_ci#   :attribute-setting
34e5c31af7Sopenharmony_ci#   macro-directive::terms
35e5c31af7Sopenharmony_ci#   +                   standalone list item continuation
36e5c31af7Sopenharmony_ci#   label::             labelled list - label must be standalone
37e5c31af7Sopenharmony_ciendPara = re.compile(r'^( *|\[.*\]|//.*|<<<<|:.*|[a-z]+::.*|\+|.*::)$')
38e5c31af7Sopenharmony_ci
39e5c31af7Sopenharmony_ci# Special case of markup ending a paragraph, used to track the current
40e5c31af7Sopenharmony_ci# command/structure. This allows for either OpenXR or Vulkan API path
41e5c31af7Sopenharmony_ci# conventions. Nominally it should use the file suffix defined by the API
42e5c31af7Sopenharmony_ci# conventions (conventions.file_suffix), except that XR uses '.txt' for
43e5c31af7Sopenharmony_ci# generated API include files, not '.adoc' like its other includes.
44e5c31af7Sopenharmony_ciincludePat = re.compile(
45e5c31af7Sopenharmony_ci        r'include::(?P<directory_traverse>((../){1,4}|\{generated\}/)(generated/)?)(?P<generated_type>[\w]+)/(?P<category>\w+)/(?P<entity_name>[^./]+).adoc[\[][\]]')
46e5c31af7Sopenharmony_ci
47e5c31af7Sopenharmony_ci# Markup that is OK in a contiguous paragraph but otherwise passed through
48e5c31af7Sopenharmony_ci#   .anything (except .., which indicates a literal block)
49e5c31af7Sopenharmony_ci#   === Section Titles
50e5c31af7Sopenharmony_ci#   image::path_to_image[attributes]  (apparently a single colon is OK but less idiomatic)
51e5c31af7Sopenharmony_ciendParaContinue = re.compile(r'^(\.[^.].*|=+ .*|image:.*\[.*\])$')
52e5c31af7Sopenharmony_ci
53e5c31af7Sopenharmony_ci# Markup for block delimiters whose contents *should* be reformatted
54e5c31af7Sopenharmony_ci#   --   (exactly two)  (open block)
55e5c31af7Sopenharmony_ci#   **** (4 or more)    (sidebar block)
56e5c31af7Sopenharmony_ci#   ==== (4 or more)    (example block)
57e5c31af7Sopenharmony_ci#   ____ (4 or more)    (quote block)
58e5c31af7Sopenharmony_ciblockTransform = re.compile(r'^(--|[*=_]{4,})$')
59e5c31af7Sopenharmony_ci
60e5c31af7Sopenharmony_ci# Fake block delimiters for "common" VU statements
61e5c31af7Sopenharmony_ciblockCommonTransform = '// Common Valid Usage\n'
62e5c31af7Sopenharmony_ci
63e5c31af7Sopenharmony_ci# Markup for block delimiters whose contents should *not* be transformed
64e5c31af7Sopenharmony_ci#   |=== (3 or more)  (table)
65e5c31af7Sopenharmony_ci#   ```  (3 or more)  (listing block)
66e5c31af7Sopenharmony_ci#   //// (4 or more)  (comment block)
67e5c31af7Sopenharmony_ci#   ---- (4 or more)  (listing block)
68e5c31af7Sopenharmony_ci#   .... (4 or more)  (literal block)
69e5c31af7Sopenharmony_ci#   ++++ (4 or more)  (passthrough block)
70e5c31af7Sopenharmony_ciblockPassthrough = re.compile(r'^(\|={3,}|[`]{3}|[\-+./]{4,})$')
71e5c31af7Sopenharmony_ci
72e5c31af7Sopenharmony_ci# Markup for introducing lists (hanging paragraphs)
73e5c31af7Sopenharmony_ci#   * bullet
74e5c31af7Sopenharmony_ci#     ** bullet
75e5c31af7Sopenharmony_ci#     -- bullet
76e5c31af7Sopenharmony_ci#   . bullet
77e5c31af7Sopenharmony_ci#   :: bullet (no longer supported by asciidoctor 2)
78e5c31af7Sopenharmony_ci#   {empty}:: bullet
79e5c31af7Sopenharmony_ci#   1. list item
80e5c31af7Sopenharmony_ci#   <1> source listing callout
81e5c31af7Sopenharmony_cibeginBullet = re.compile(r'^ *([-*.]+|\{empty\}::|::|[0-9]+[.]|<([0-9]+)>) ')
82e5c31af7Sopenharmony_ci
83e5c31af7Sopenharmony_ciclass TransformState:
84e5c31af7Sopenharmony_ci    """State machine for transforming documents.
85e5c31af7Sopenharmony_ci
86e5c31af7Sopenharmony_ci    Represents the state of the transform operation"""
87e5c31af7Sopenharmony_ci    def __init__(self):
88e5c31af7Sopenharmony_ci        self.blockStack = [ None ]
89e5c31af7Sopenharmony_ci        """The last element is a line with the asciidoc block delimiter that is
90e5c31af7Sopenharmony_ci        currently in effect, such as '--', '----', '****', '====', or '++++'.
91e5c31af7Sopenharmony_ci        This affects whether or not the block contents should be transformed."""
92e5c31af7Sopenharmony_ci        self.transformStack = [ True ]
93e5c31af7Sopenharmony_ci        """The last element is True or False if the current blockStack contents
94e5c31af7Sopenharmony_ci        should be transformed."""
95e5c31af7Sopenharmony_ci        self.vuStack = [ False ]
96e5c31af7Sopenharmony_ci        """the last element is True or False if the current blockStack contents
97e5c31af7Sopenharmony_ci        are an explicit Valid Usage block."""
98e5c31af7Sopenharmony_ci
99e5c31af7Sopenharmony_ci        self.para = []
100e5c31af7Sopenharmony_ci        """list of lines in the paragraph being accumulated.
101e5c31af7Sopenharmony_ci        When this is non-empty, there is a current paragraph."""
102e5c31af7Sopenharmony_ci
103e5c31af7Sopenharmony_ci        self.lastTitle = False
104e5c31af7Sopenharmony_ci        """true if the previous line was a document title line
105e5c31af7Sopenharmony_ci        (e.g. :leveloffset: 0 - no attempt to track changes to this is made)."""
106e5c31af7Sopenharmony_ci
107e5c31af7Sopenharmony_ci        self.leadIndent = 0
108e5c31af7Sopenharmony_ci        """indent level (in spaces) of the first line of a paragraph."""
109e5c31af7Sopenharmony_ci
110e5c31af7Sopenharmony_ci        self.hangIndent = 0
111e5c31af7Sopenharmony_ci        """indent level of the remaining lines of a paragraph."""
112e5c31af7Sopenharmony_ci
113e5c31af7Sopenharmony_ci        self.lineNumber = 0
114e5c31af7Sopenharmony_ci        """line number being read from the input file."""
115e5c31af7Sopenharmony_ci
116e5c31af7Sopenharmony_ci        self.defaultApiName = '{refpage}'
117e5c31af7Sopenharmony_ci        self.apiName = self.defaultApiName
118e5c31af7Sopenharmony_ci        """String name of an API structure or command for VUID tag generation,
119e5c31af7Sopenharmony_ci        or {refpage} if one has not been included in this file yet."""
120e5c31af7Sopenharmony_ci
121e5c31af7Sopenharmony_ci    def incrLineNumber(self):
122e5c31af7Sopenharmony_ci        self.lineNumber = self.lineNumber + 1
123e5c31af7Sopenharmony_ci
124e5c31af7Sopenharmony_ci    def isOpenBlockDelimiter(self, line):
125e5c31af7Sopenharmony_ci        """Returns True if line is an open block delimiter.
126e5c31af7Sopenharmony_ci           This does not and should not match the listing block delimiter,
127e5c31af7Sopenharmony_ci           which is used inside refpage blocks both as a listing block and,
128e5c31af7Sopenharmony_ci           via an extension, as a nested open block."""
129e5c31af7Sopenharmony_ci        return line.rstrip() == '--'
130e5c31af7Sopenharmony_ci
131e5c31af7Sopenharmony_ci    def resetPara(self):
132e5c31af7Sopenharmony_ci        """Reset the paragraph, including its indentation level"""
133e5c31af7Sopenharmony_ci        self.para = []
134e5c31af7Sopenharmony_ci        self.leadIndent = 0
135e5c31af7Sopenharmony_ci        self.hangIndent = 0
136e5c31af7Sopenharmony_ci
137e5c31af7Sopenharmony_ci    def endBlock(self, line, transform, vuBlock):
138e5c31af7Sopenharmony_ci        """If beginning a block, tag whether or not to transform the contents.
139e5c31af7Sopenharmony_ci
140e5c31af7Sopenharmony_ci        vuBlock is True if the previous line indicates this is a Valid Usage
141e5c31af7Sopenharmony_ci        block."""
142e5c31af7Sopenharmony_ci        if self.blockStack[-1] == line:
143e5c31af7Sopenharmony_ci            logDiag('endBlock line', self.lineNumber,
144e5c31af7Sopenharmony_ci                    ': popping block end depth:', len(self.blockStack),
145e5c31af7Sopenharmony_ci                    ':', line, end='')
146e5c31af7Sopenharmony_ci
147e5c31af7Sopenharmony_ci            # Reset apiName at the end of an open block.
148e5c31af7Sopenharmony_ci            # Open blocks cannot be nested (at present), so this is safe.
149e5c31af7Sopenharmony_ci            if self.isOpenBlockDelimiter(line):
150e5c31af7Sopenharmony_ci                logDiag('reset apiName to empty at line', self.lineNumber)
151e5c31af7Sopenharmony_ci                self.apiName = self.defaultApiName
152e5c31af7Sopenharmony_ci            else:
153e5c31af7Sopenharmony_ci                logDiag('NOT resetting apiName to default at line',
154e5c31af7Sopenharmony_ci                        self.lineNumber)
155e5c31af7Sopenharmony_ci
156e5c31af7Sopenharmony_ci            self.blockStack.pop()
157e5c31af7Sopenharmony_ci            self.transformStack.pop()
158e5c31af7Sopenharmony_ci            self.vuStack.pop()
159e5c31af7Sopenharmony_ci        else:
160e5c31af7Sopenharmony_ci            # Start a block
161e5c31af7Sopenharmony_ci            self.blockStack.append(line)
162e5c31af7Sopenharmony_ci            self.transformStack.append(transform)
163e5c31af7Sopenharmony_ci            self.vuStack.append(vuBlock)
164e5c31af7Sopenharmony_ci
165e5c31af7Sopenharmony_ci            logDiag('endBlock transform =', transform, ' line', self.lineNumber,
166e5c31af7Sopenharmony_ci                    ': pushing block start depth', len(self.blockStack),
167e5c31af7Sopenharmony_ci                    ':', line, end='')
168e5c31af7Sopenharmony_ci
169e5c31af7Sopenharmony_ci    def addLine(self, line, indent):
170e5c31af7Sopenharmony_ci        """Add a line to the current paragraph"""
171e5c31af7Sopenharmony_ci        if self.para == []:
172e5c31af7Sopenharmony_ci            # Begin a new paragraph
173e5c31af7Sopenharmony_ci            self.para = [line]
174e5c31af7Sopenharmony_ci            self.leadIndent = indent
175e5c31af7Sopenharmony_ci            self.hangIndent = indent
176e5c31af7Sopenharmony_ci        else:
177e5c31af7Sopenharmony_ci            # Add a line to a paragraph. Increase the hanging indentation
178e5c31af7Sopenharmony_ci            # level - once.
179e5c31af7Sopenharmony_ci            if self.hangIndent == self.leadIndent:
180e5c31af7Sopenharmony_ci                self.hangIndent = indent
181e5c31af7Sopenharmony_ci            self.para.append(line)
182e5c31af7Sopenharmony_ci
183e5c31af7Sopenharmony_ci
184e5c31af7Sopenharmony_ciclass TransformCallbackState:
185e5c31af7Sopenharmony_ci    """State given to the transformer callback object, derived from
186e5c31af7Sopenharmony_ci    TransformState."""
187e5c31af7Sopenharmony_ci    def __init__(self, state):
188e5c31af7Sopenharmony_ci        self.isVU = state.vuStack[-1] if len(state.vuStack) > 0 else False
189e5c31af7Sopenharmony_ci        """Whether this paragraph is a VU."""
190e5c31af7Sopenharmony_ci
191e5c31af7Sopenharmony_ci        self.apiName = state.apiName
192e5c31af7Sopenharmony_ci        """String name of an API structure or command this paragraph belongs
193e5c31af7Sopenharmony_ci        to."""
194e5c31af7Sopenharmony_ci
195e5c31af7Sopenharmony_ci        self.leadIndent = state.leadIndent
196e5c31af7Sopenharmony_ci        """indent level (in spaces) of the first line of a paragraph."""
197e5c31af7Sopenharmony_ci
198e5c31af7Sopenharmony_ci        self.hangIndent = state.hangIndent
199e5c31af7Sopenharmony_ci        """indent level of the remaining lines of a paragraph."""
200e5c31af7Sopenharmony_ci
201e5c31af7Sopenharmony_ci        self.lineNumber = state.lineNumber
202e5c31af7Sopenharmony_ci        """line number being read from the input file."""
203e5c31af7Sopenharmony_ci
204e5c31af7Sopenharmony_ci
205e5c31af7Sopenharmony_ciclass DocTransformer:
206e5c31af7Sopenharmony_ci    """A transformer that recursively goes over all spec files under a path.
207e5c31af7Sopenharmony_ci
208e5c31af7Sopenharmony_ci    The transformer goes over all spec files under a path and does some basic
209e5c31af7Sopenharmony_ci    parsing.  In particular, it tracks which section the current text belongs
210e5c31af7Sopenharmony_ci    to, whether it references a VU, etc and processes them in 'paragraph'
211e5c31af7Sopenharmony_ci    granularity.
212e5c31af7Sopenharmony_ci    The transformer takes a callback object with the following methods:
213e5c31af7Sopenharmony_ci
214e5c31af7Sopenharmony_ci    - transformParagraph: Called when a paragraph is parsed.  The paragraph
215e5c31af7Sopenharmony_ci      along with some information (such as whether it is a VU) is passed.  The
216e5c31af7Sopenharmony_ci      function may transform the paragraph as necessary.
217e5c31af7Sopenharmony_ci    - onEmbeddedVUConditional: Called when an embedded VU conditional is
218e5c31af7Sopenharmony_ci      encountered.
219e5c31af7Sopenharmony_ci    """
220e5c31af7Sopenharmony_ci    def __init__(self,
221e5c31af7Sopenharmony_ci                 filename,
222e5c31af7Sopenharmony_ci                 outfile,
223e5c31af7Sopenharmony_ci                 callback):
224e5c31af7Sopenharmony_ci        self.filename = filename
225e5c31af7Sopenharmony_ci        """base name of file being read from."""
226e5c31af7Sopenharmony_ci
227e5c31af7Sopenharmony_ci        self.outfile = outfile
228e5c31af7Sopenharmony_ci        """file handle to write to."""
229e5c31af7Sopenharmony_ci
230e5c31af7Sopenharmony_ci        self.state = TransformState()
231e5c31af7Sopenharmony_ci        """State of transformation"""
232e5c31af7Sopenharmony_ci
233e5c31af7Sopenharmony_ci        self.callback = callback
234e5c31af7Sopenharmony_ci        """The transformation callback object"""
235e5c31af7Sopenharmony_ci
236e5c31af7Sopenharmony_ci    def printLines(self, lines):
237e5c31af7Sopenharmony_ci        """Print an array of lines with newlines already present"""
238e5c31af7Sopenharmony_ci        if len(lines) > 0:
239e5c31af7Sopenharmony_ci            logDiag(':: printLines:', len(lines), 'lines: ', lines[0], end='')
240e5c31af7Sopenharmony_ci
241e5c31af7Sopenharmony_ci        if self.outfile is not None:
242e5c31af7Sopenharmony_ci            for line in lines:
243e5c31af7Sopenharmony_ci                print(line, file=self.outfile, end='')
244e5c31af7Sopenharmony_ci
245e5c31af7Sopenharmony_ci    def emitPara(self):
246e5c31af7Sopenharmony_ci        """Emit a paragraph, possibly transforming it depending on the block
247e5c31af7Sopenharmony_ci        context.
248e5c31af7Sopenharmony_ci
249e5c31af7Sopenharmony_ci        Resets the paragraph accumulator."""
250e5c31af7Sopenharmony_ci        if self.state.para != []:
251e5c31af7Sopenharmony_ci            transformedPara = self.state.para
252e5c31af7Sopenharmony_ci
253e5c31af7Sopenharmony_ci            if self.state.transformStack[-1]:
254e5c31af7Sopenharmony_ci                callbackState = TransformCallbackState(self.state)
255e5c31af7Sopenharmony_ci
256e5c31af7Sopenharmony_ci                transformedPara = self.callback.transformParagraph(
257e5c31af7Sopenharmony_ci                        self.state.para,
258e5c31af7Sopenharmony_ci                        callbackState)
259e5c31af7Sopenharmony_ci
260e5c31af7Sopenharmony_ci            self.printLines(transformedPara)
261e5c31af7Sopenharmony_ci
262e5c31af7Sopenharmony_ci        self.state.resetPara()
263e5c31af7Sopenharmony_ci
264e5c31af7Sopenharmony_ci    def endPara(self, line):
265e5c31af7Sopenharmony_ci        """'line' ends a paragraph and should itself be emitted.
266e5c31af7Sopenharmony_ci        line may be None to indicate EOF or other exception."""
267e5c31af7Sopenharmony_ci        logDiag('endPara line', self.state.lineNumber, ': emitting paragraph')
268e5c31af7Sopenharmony_ci
269e5c31af7Sopenharmony_ci        # Emit current paragraph, this line, and reset tracker
270e5c31af7Sopenharmony_ci        self.emitPara()
271e5c31af7Sopenharmony_ci
272e5c31af7Sopenharmony_ci        if line:
273e5c31af7Sopenharmony_ci            self.printLines([line])
274e5c31af7Sopenharmony_ci
275e5c31af7Sopenharmony_ci    def endParaContinue(self, line):
276e5c31af7Sopenharmony_ci        """'line' ends a paragraph (unless there is already a paragraph being
277e5c31af7Sopenharmony_ci        accumulated, e.g. len(para) > 0 - currently not implemented)"""
278e5c31af7Sopenharmony_ci        self.endPara(line)
279e5c31af7Sopenharmony_ci
280e5c31af7Sopenharmony_ci    def endBlock(self, line, transform = False, vuBlock = False):
281e5c31af7Sopenharmony_ci        """'line' begins or ends a block.
282e5c31af7Sopenharmony_ci
283e5c31af7Sopenharmony_ci        If beginning a block, tag whether or not to transform the contents.
284e5c31af7Sopenharmony_ci
285e5c31af7Sopenharmony_ci        vuBlock is True if the previous line indicates this is a Valid Usage
286e5c31af7Sopenharmony_ci        block."""
287e5c31af7Sopenharmony_ci        self.endPara(line)
288e5c31af7Sopenharmony_ci        self.state.endBlock(line, transform, vuBlock)
289e5c31af7Sopenharmony_ci
290e5c31af7Sopenharmony_ci    def endParaBlockTransform(self, line, vuBlock):
291e5c31af7Sopenharmony_ci        """'line' begins or ends a block. The paragraphs in the block *should* be
292e5c31af7Sopenharmony_ci        reformatted (e.g. a NOTE)."""
293e5c31af7Sopenharmony_ci        self.endBlock(line, transform = True, vuBlock = vuBlock)
294e5c31af7Sopenharmony_ci
295e5c31af7Sopenharmony_ci    def endParaBlockPassthrough(self, line):
296e5c31af7Sopenharmony_ci        """'line' begins or ends a block. The paragraphs in the block should
297e5c31af7Sopenharmony_ci        *not* be reformatted (e.g. a code listing)."""
298e5c31af7Sopenharmony_ci        self.endBlock(line, transform = False)
299e5c31af7Sopenharmony_ci
300e5c31af7Sopenharmony_ci    def addLine(self, line):
301e5c31af7Sopenharmony_ci        """'line' starts or continues a paragraph.
302e5c31af7Sopenharmony_ci
303e5c31af7Sopenharmony_ci        Paragraphs may have "hanging indent", e.g.
304e5c31af7Sopenharmony_ci
305e5c31af7Sopenharmony_ci        ```
306e5c31af7Sopenharmony_ci          * Bullet point...
307e5c31af7Sopenharmony_ci            ... continued
308e5c31af7Sopenharmony_ci        ```
309e5c31af7Sopenharmony_ci
310e5c31af7Sopenharmony_ci        In this case, when the higher indentation level ends, so does the
311e5c31af7Sopenharmony_ci        paragraph."""
312e5c31af7Sopenharmony_ci        logDiag('addLine line', self.state.lineNumber, ':', line, end='')
313e5c31af7Sopenharmony_ci
314e5c31af7Sopenharmony_ci        # See https://stackoverflow.com/questions/13648813/what-is-the-pythonic-way-to-count-the-leading-spaces-in-a-string
315e5c31af7Sopenharmony_ci        indent = len(line) - len(line.lstrip())
316e5c31af7Sopenharmony_ci
317e5c31af7Sopenharmony_ci        # A hanging paragraph ends due to a less-indented line.
318e5c31af7Sopenharmony_ci        if self.state.para != [] and indent < self.state.hangIndent:
319e5c31af7Sopenharmony_ci            logDiag('addLine: line reduces indentation, emit paragraph')
320e5c31af7Sopenharmony_ci            self.emitPara()
321e5c31af7Sopenharmony_ci
322e5c31af7Sopenharmony_ci        # A bullet point (or something that looks like one) always ends the
323e5c31af7Sopenharmony_ci        # current paragraph.
324e5c31af7Sopenharmony_ci        if beginBullet.match(line):
325e5c31af7Sopenharmony_ci            logDiag('addLine: line matches beginBullet, emit paragraph')
326e5c31af7Sopenharmony_ci            self.emitPara()
327e5c31af7Sopenharmony_ci
328e5c31af7Sopenharmony_ci        self.state.addLine(line, indent)
329e5c31af7Sopenharmony_ci
330e5c31af7Sopenharmony_ci    def apiMatch(self, oldname, newname):
331e5c31af7Sopenharmony_ci        """Returns whether oldname and newname match, up to an API suffix.
332e5c31af7Sopenharmony_ci           This should use the API map instead of this heuristic, since aliases
333e5c31af7Sopenharmony_ci           like VkPhysicalDeviceVariablePointerFeaturesKHR ->
334e5c31af7Sopenharmony_ci           VkPhysicalDeviceVariablePointersFeatures are not recognized."""
335e5c31af7Sopenharmony_ci        upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
336e5c31af7Sopenharmony_ci        return oldname.rstrip(upper) == newname.rstrip(upper)
337e5c31af7Sopenharmony_ci
338e5c31af7Sopenharmony_ci    def transformFile(self, lines):
339e5c31af7Sopenharmony_ci        """Transform lines, and possibly output to to the given file."""
340e5c31af7Sopenharmony_ci
341e5c31af7Sopenharmony_ci        for line in lines:
342e5c31af7Sopenharmony_ci            self.state.incrLineNumber()
343e5c31af7Sopenharmony_ci
344e5c31af7Sopenharmony_ci            # Is this a title line (leading '= ' followed by text)?
345e5c31af7Sopenharmony_ci            thisTitle = False
346e5c31af7Sopenharmony_ci
347e5c31af7Sopenharmony_ci            # The logic here is broken. If we are in a non-transformable block and
348e5c31af7Sopenharmony_ci            # this line *does not* end the block, it should always be
349e5c31af7Sopenharmony_ci            # accumulated.
350e5c31af7Sopenharmony_ci
351e5c31af7Sopenharmony_ci            # Test for a blockCommonTransform delimiter comment first, to avoid
352e5c31af7Sopenharmony_ci            # treating it solely as a end-Paragraph marker comment.
353e5c31af7Sopenharmony_ci            if line == blockCommonTransform:
354e5c31af7Sopenharmony_ci                # Starting or ending a pseudo-block for "common" VU statements.
355e5c31af7Sopenharmony_ci                self.endParaBlockTransform(line, vuBlock = True)
356e5c31af7Sopenharmony_ci
357e5c31af7Sopenharmony_ci            elif blockTransform.match(line):
358e5c31af7Sopenharmony_ci                # Starting or ending a block whose contents may be transformed.
359e5c31af7Sopenharmony_ci                # Blocks cannot be nested.
360e5c31af7Sopenharmony_ci
361e5c31af7Sopenharmony_ci                # Is this is an explicit Valid Usage block?
362e5c31af7Sopenharmony_ci                vuBlock = (self.state.lineNumber > 1 and
363e5c31af7Sopenharmony_ci                           lines[self.state.lineNumber-2] == '.Valid Usage\n')
364e5c31af7Sopenharmony_ci
365e5c31af7Sopenharmony_ci                self.endParaBlockTransform(line, vuBlock)
366e5c31af7Sopenharmony_ci
367e5c31af7Sopenharmony_ci            elif endPara.match(line):
368e5c31af7Sopenharmony_ci                # Ending a paragraph. Emit the current paragraph, if any, and
369e5c31af7Sopenharmony_ci                # prepare to begin a new paragraph.
370e5c31af7Sopenharmony_ci
371e5c31af7Sopenharmony_ci                self.endPara(line)
372e5c31af7Sopenharmony_ci
373e5c31af7Sopenharmony_ci                # If this is an include:: line starting the definition of a
374e5c31af7Sopenharmony_ci                # structure or command, track that for use in VUID generation.
375e5c31af7Sopenharmony_ci
376e5c31af7Sopenharmony_ci                matches = includePat.search(line)
377e5c31af7Sopenharmony_ci                if matches is not None:
378e5c31af7Sopenharmony_ci                    generated_type = matches.group('generated_type')
379e5c31af7Sopenharmony_ci                    include_type = matches.group('category')
380e5c31af7Sopenharmony_ci                    if generated_type == 'api' and include_type in ('protos', 'structs', 'funcpointers'):
381e5c31af7Sopenharmony_ci                        apiName = matches.group('entity_name')
382e5c31af7Sopenharmony_ci                        if self.state.apiName != self.state.defaultApiName:
383e5c31af7Sopenharmony_ci                            # This happens when there are multiple API include
384e5c31af7Sopenharmony_ci                            # lines in a single block. The style guideline is to
385e5c31af7Sopenharmony_ci                            # always place the API which others are promoted to
386e5c31af7Sopenharmony_ci                            # first. In virtually all cases, the promoted API
387e5c31af7Sopenharmony_ci                            # will differ solely in the vendor suffix (or
388e5c31af7Sopenharmony_ci                            # absence of it), which is benign.
389e5c31af7Sopenharmony_ci                            if not self.apiMatch(self.state.apiName, apiName):
390e5c31af7Sopenharmony_ci                                logDiag(f'Promoted API name mismatch at line {self.state.lineNumber}: {apiName} does not match self.state.apiName (this is OK if it is just a spelling alias)')
391e5c31af7Sopenharmony_ci                        else:
392e5c31af7Sopenharmony_ci                            self.state.apiName = apiName
393e5c31af7Sopenharmony_ci
394e5c31af7Sopenharmony_ci            elif endParaContinue.match(line):
395e5c31af7Sopenharmony_ci                # For now, always just end the paragraph.
396e5c31af7Sopenharmony_ci                # Could check see if len(para) > 0 to accumulate.
397e5c31af7Sopenharmony_ci
398e5c31af7Sopenharmony_ci                self.endParaContinue(line)
399e5c31af7Sopenharmony_ci
400e5c31af7Sopenharmony_ci                # If it is a title line, track that
401e5c31af7Sopenharmony_ci                if line[0:2] == '= ':
402e5c31af7Sopenharmony_ci                    thisTitle = True
403e5c31af7Sopenharmony_ci
404e5c31af7Sopenharmony_ci            elif blockPassthrough.match(line):
405e5c31af7Sopenharmony_ci                # Starting or ending a block whose contents must not be
406e5c31af7Sopenharmony_ci                # transformed.  These are tables, etc. Blocks cannot be nested.
407e5c31af7Sopenharmony_ci                # Note that the use of a listing block masquerading as an
408e5c31af7Sopenharmony_ci                # open block, via an extension, will not be formatted even
409e5c31af7Sopenharmony_ci                # though it should be.
410e5c31af7Sopenharmony_ci                # Fixing this would require looking at the previous line
411e5c31af7Sopenharmony_ci                # state for the '[open]' tag, and there are so few cases of
412e5c31af7Sopenharmony_ci                # this in the spec markup that it is not worth the trouble.
413e5c31af7Sopenharmony_ci
414e5c31af7Sopenharmony_ci                self.endParaBlockPassthrough(line)
415e5c31af7Sopenharmony_ci            elif self.state.lastTitle:
416e5c31af7Sopenharmony_ci                # The previous line was a document title line. This line
417e5c31af7Sopenharmony_ci                # is the author / credits line and must not be transformed.
418e5c31af7Sopenharmony_ci
419e5c31af7Sopenharmony_ci                self.endPara(line)
420e5c31af7Sopenharmony_ci            else:
421e5c31af7Sopenharmony_ci                # Just accumulate a line to the current paragraph. Watch out for
422e5c31af7Sopenharmony_ci                # hanging indents / bullet-points and track that indent level.
423e5c31af7Sopenharmony_ci
424e5c31af7Sopenharmony_ci                self.addLine(line)
425e5c31af7Sopenharmony_ci
426e5c31af7Sopenharmony_ci                # Commented out now that VU extractor supports this, but may
427e5c31af7Sopenharmony_ci                # need to refactor through a conventions object enable if
428e5c31af7Sopenharmony_ci                # OpenXR still needs this.
429e5c31af7Sopenharmony_ci
430e5c31af7Sopenharmony_ci                # This test looks for disallowed conditionals inside Valid Usage
431e5c31af7Sopenharmony_ci                # blocks, by checking if (a) this line does not start a new VU
432e5c31af7Sopenharmony_ci                # (bullet point) and (b) the previous line starts an asciidoctor
433e5c31af7Sopenharmony_ci                # conditional (ifdef:: or ifndef::).
434e5c31af7Sopenharmony_ci                # if (self.state.vuStack[-1]
435e5c31af7Sopenharmony_ci                #     and not beginBullet.match(line)
436e5c31af7Sopenharmony_ci                #     and conditionalStart.match(lines[self.state.lineNumber-2])):
437e5c31af7Sopenharmony_ci                #        self.callback.onEmbeddedVUConditional(self.state)
438e5c31af7Sopenharmony_ci
439e5c31af7Sopenharmony_ci            self.state.lastTitle = thisTitle
440e5c31af7Sopenharmony_ci
441e5c31af7Sopenharmony_ci        # Cleanup at end of file
442e5c31af7Sopenharmony_ci        self.endPara(None)
443e5c31af7Sopenharmony_ci
444e5c31af7Sopenharmony_ci        # Check for sensible block nesting
445e5c31af7Sopenharmony_ci        if len(self.state.blockStack) > 1:
446e5c31af7Sopenharmony_ci            logWarn('file', self.filename,
447e5c31af7Sopenharmony_ci                    'mismatched asciidoc block delimiters at EOF:',
448e5c31af7Sopenharmony_ci                    self.state.blockStack[-1])
449e5c31af7Sopenharmony_ci
450