1e5c31af7Sopenharmony_ci#!/usr/bin/python3
2e5c31af7Sopenharmony_ci#
3e5c31af7Sopenharmony_ci# Copyright 2016-2024 The Khronos Group Inc.
4e5c31af7Sopenharmony_ci#
5e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
6e5c31af7Sopenharmony_ci
7e5c31af7Sopenharmony_ci# Utility functions for automatic ref page generation and other script stuff
8e5c31af7Sopenharmony_ci
9e5c31af7Sopenharmony_ciimport io
10e5c31af7Sopenharmony_ciimport re
11e5c31af7Sopenharmony_ciimport sys
12e5c31af7Sopenharmony_ciimport subprocess
13e5c31af7Sopenharmony_ci
14e5c31af7Sopenharmony_ci# global errFile, warnFile, diagFile
15e5c31af7Sopenharmony_ci
16e5c31af7Sopenharmony_cierrFile = sys.stderr
17e5c31af7Sopenharmony_ciwarnFile = sys.stdout
18e5c31af7Sopenharmony_cidiagFile = None
19e5c31af7Sopenharmony_cilogSourcefile = None
20e5c31af7Sopenharmony_cilogProcname = None
21e5c31af7Sopenharmony_cilogLine = None
22e5c31af7Sopenharmony_ci
23e5c31af7Sopenharmony_cidef unescapeQuotes(s):
24e5c31af7Sopenharmony_ci    """Remove \' escape sequences in a string (refpage description)"""
25e5c31af7Sopenharmony_ci    return s.replace('\\\'', '\'')
26e5c31af7Sopenharmony_ci
27e5c31af7Sopenharmony_cidef write(*args, **kwargs ):
28e5c31af7Sopenharmony_ci    file = kwargs.pop('file',sys.stdout)
29e5c31af7Sopenharmony_ci    end = kwargs.pop('end','\n')
30e5c31af7Sopenharmony_ci    file.write(' '.join(str(arg) for arg in args))
31e5c31af7Sopenharmony_ci    file.write(end)
32e5c31af7Sopenharmony_ci
33e5c31af7Sopenharmony_cidef setLogSourcefile(filename):
34e5c31af7Sopenharmony_ci    """Metadata which may be printed (if not None) for diagnostic messages"""
35e5c31af7Sopenharmony_ci    global logSourcefile
36e5c31af7Sopenharmony_ci    logSourcefile = filename
37e5c31af7Sopenharmony_ci
38e5c31af7Sopenharmony_cidef setLogProcname(procname):
39e5c31af7Sopenharmony_ci    global logProcname
40e5c31af7Sopenharmony_ci    logProcname = procname
41e5c31af7Sopenharmony_ci
42e5c31af7Sopenharmony_cidef setLogLine(line):
43e5c31af7Sopenharmony_ci    global logLine
44e5c31af7Sopenharmony_ci    logLine = line
45e5c31af7Sopenharmony_ci
46e5c31af7Sopenharmony_cidef logHeader(severity):
47e5c31af7Sopenharmony_ci    """Generate prefix for a diagnostic line using metadata and severity"""
48e5c31af7Sopenharmony_ci    global logSourcefile, logProcname, logLine
49e5c31af7Sopenharmony_ci
50e5c31af7Sopenharmony_ci    msg = severity + ': '
51e5c31af7Sopenharmony_ci    if logProcname:
52e5c31af7Sopenharmony_ci        msg = msg + ' in ' + logProcname
53e5c31af7Sopenharmony_ci    if logSourcefile:
54e5c31af7Sopenharmony_ci        msg = msg + ' for ' + logSourcefile
55e5c31af7Sopenharmony_ci    if logLine:
56e5c31af7Sopenharmony_ci        msg = msg + ' line ' + str(logLine)
57e5c31af7Sopenharmony_ci    return msg + ' '
58e5c31af7Sopenharmony_ci
59e5c31af7Sopenharmony_cidef setLogFile(setDiag, setWarn, filename):
60e5c31af7Sopenharmony_ci    """Set the file handle to log either or both warnings and diagnostics to.
61e5c31af7Sopenharmony_ci
62e5c31af7Sopenharmony_ci    - setDiag and setWarn are True if the corresponding handle is to be set.
63e5c31af7Sopenharmony_ci    - filename is None for no logging, '-' for stdout, or a pathname."""
64e5c31af7Sopenharmony_ci    global diagFile, warnFile
65e5c31af7Sopenharmony_ci
66e5c31af7Sopenharmony_ci    if filename is None:
67e5c31af7Sopenharmony_ci        return
68e5c31af7Sopenharmony_ci
69e5c31af7Sopenharmony_ci    if filename == '-':
70e5c31af7Sopenharmony_ci        fp = sys.stdout
71e5c31af7Sopenharmony_ci    else:
72e5c31af7Sopenharmony_ci        fp = open(filename, 'w', encoding='utf-8')
73e5c31af7Sopenharmony_ci
74e5c31af7Sopenharmony_ci    if setDiag:
75e5c31af7Sopenharmony_ci        diagFile = fp
76e5c31af7Sopenharmony_ci    if setWarn:
77e5c31af7Sopenharmony_ci        warnFile = fp
78e5c31af7Sopenharmony_ci
79e5c31af7Sopenharmony_cidef logDiag(*args, **kwargs):
80e5c31af7Sopenharmony_ci    file = kwargs.pop('file', diagFile)
81e5c31af7Sopenharmony_ci    end = kwargs.pop('end','\n')
82e5c31af7Sopenharmony_ci    if file is not None:
83e5c31af7Sopenharmony_ci        file.write(logHeader('DIAG') + ' '.join(str(arg) for arg in args))
84e5c31af7Sopenharmony_ci        file.write(end)
85e5c31af7Sopenharmony_ci
86e5c31af7Sopenharmony_cidef logWarn(*args, **kwargs):
87e5c31af7Sopenharmony_ci    file = kwargs.pop('file', warnFile)
88e5c31af7Sopenharmony_ci    end = kwargs.pop('end','\n')
89e5c31af7Sopenharmony_ci    if file is not None:
90e5c31af7Sopenharmony_ci        file.write(logHeader('WARN') + ' '.join(str(arg) for arg in args))
91e5c31af7Sopenharmony_ci        file.write(end)
92e5c31af7Sopenharmony_ci
93e5c31af7Sopenharmony_cidef logErr(*args, **kwargs):
94e5c31af7Sopenharmony_ci    file = kwargs.pop('file', errFile)
95e5c31af7Sopenharmony_ci    end = kwargs.pop('end','\n')
96e5c31af7Sopenharmony_ci
97e5c31af7Sopenharmony_ci    strfile = io.StringIO()
98e5c31af7Sopenharmony_ci    strfile.write(logHeader('ERROR') + ' '.join(str(arg) for arg in args))
99e5c31af7Sopenharmony_ci    strfile.write(end)
100e5c31af7Sopenharmony_ci
101e5c31af7Sopenharmony_ci    if file is not None:
102e5c31af7Sopenharmony_ci        file.write(strfile.getvalue())
103e5c31af7Sopenharmony_ci    raise UserWarning(strfile.getvalue())
104e5c31af7Sopenharmony_ci
105e5c31af7Sopenharmony_cidef isempty(s):
106e5c31af7Sopenharmony_ci    """Return True if s is nothing but white space, False otherwise"""
107e5c31af7Sopenharmony_ci    return len(''.join(s.split())) == 0
108e5c31af7Sopenharmony_ci
109e5c31af7Sopenharmony_ciclass pageInfo:
110e5c31af7Sopenharmony_ci    """Information about a ref page relative to the file it is extracted from."""
111e5c31af7Sopenharmony_ci    def __init__(self):
112e5c31af7Sopenharmony_ci        self.extractPage = True
113e5c31af7Sopenharmony_ci        """True if page should be extracted"""
114e5c31af7Sopenharmony_ci
115e5c31af7Sopenharmony_ci        self.Warning  = None
116e5c31af7Sopenharmony_ci        """string warning if page is suboptimal or cannot be generated"""
117e5c31af7Sopenharmony_ci
118e5c31af7Sopenharmony_ci        self.embed    = False
119e5c31af7Sopenharmony_ci        """False or the name of the ref page this include is embedded within"""
120e5c31af7Sopenharmony_ci
121e5c31af7Sopenharmony_ci        self.type     = None
122e5c31af7Sopenharmony_ci        """refpage type attribute - 'structs', 'protos', 'freeform', etc."""
123e5c31af7Sopenharmony_ci
124e5c31af7Sopenharmony_ci        self.name     = None
125e5c31af7Sopenharmony_ci        """struct/proto/enumerant/etc. name"""
126e5c31af7Sopenharmony_ci
127e5c31af7Sopenharmony_ci        self.desc     = None
128e5c31af7Sopenharmony_ci        """short description of ref page"""
129e5c31af7Sopenharmony_ci
130e5c31af7Sopenharmony_ci        self.begin    = None
131e5c31af7Sopenharmony_ci        """index of first line of the page (heuristic or // refBegin)"""
132e5c31af7Sopenharmony_ci
133e5c31af7Sopenharmony_ci        self.include  = None
134e5c31af7Sopenharmony_ci        """index of include:: line defining the page"""
135e5c31af7Sopenharmony_ci
136e5c31af7Sopenharmony_ci        self.param    = None
137e5c31af7Sopenharmony_ci        """index of first line of parameter/member definitions"""
138e5c31af7Sopenharmony_ci
139e5c31af7Sopenharmony_ci        self.body     = None
140e5c31af7Sopenharmony_ci        """index of first line of body text"""
141e5c31af7Sopenharmony_ci
142e5c31af7Sopenharmony_ci        self.validity = None
143e5c31af7Sopenharmony_ci        """index of validity include"""
144e5c31af7Sopenharmony_ci
145e5c31af7Sopenharmony_ci        self.end      = None
146e5c31af7Sopenharmony_ci        """index of last line of the page (heuristic validity include, or // refEnd)"""
147e5c31af7Sopenharmony_ci
148e5c31af7Sopenharmony_ci        self.alias    = ''
149e5c31af7Sopenharmony_ci        """aliases of this name, if supplied, or ''"""
150e5c31af7Sopenharmony_ci
151e5c31af7Sopenharmony_ci        self.refs     = ''
152e5c31af7Sopenharmony_ci        """cross-references on // refEnd line, if supplied"""
153e5c31af7Sopenharmony_ci
154e5c31af7Sopenharmony_ci        self.spec     = None
155e5c31af7Sopenharmony_ci        """'spec' attribute in refpage open block, if supplied, or None for the default ('api') type"""
156e5c31af7Sopenharmony_ci
157e5c31af7Sopenharmony_ci        self.anchor   = None
158e5c31af7Sopenharmony_ci        """'anchor' attribute in refpage open block, if supplied, or inferred to be the same as the 'name'"""
159e5c31af7Sopenharmony_ci
160e5c31af7Sopenharmony_cidef printPageInfoField(desc, line, file):
161e5c31af7Sopenharmony_ci    """Print a single field of a pageInfo struct, possibly None.
162e5c31af7Sopenharmony_ci
163e5c31af7Sopenharmony_ci    - desc - string description of field
164e5c31af7Sopenharmony_ci    - line - field value or None
165e5c31af7Sopenharmony_ci    - file - indexed by line"""
166e5c31af7Sopenharmony_ci    if line is not None:
167e5c31af7Sopenharmony_ci        logDiag(desc + ':', line + 1, '\t-> ', file[line], end='')
168e5c31af7Sopenharmony_ci    else:
169e5c31af7Sopenharmony_ci        logDiag(desc + ':', line)
170e5c31af7Sopenharmony_ci
171e5c31af7Sopenharmony_cidef printPageInfo(pi, file):
172e5c31af7Sopenharmony_ci    """Print out fields of a pageInfo struct
173e5c31af7Sopenharmony_ci
174e5c31af7Sopenharmony_ci    - pi - pageInfo
175e5c31af7Sopenharmony_ci    - file - indexed by pageInfo"""
176e5c31af7Sopenharmony_ci    logDiag('TYPE:   ', pi.type)
177e5c31af7Sopenharmony_ci    logDiag('NAME:   ', pi.name)
178e5c31af7Sopenharmony_ci    logDiag('WARNING:', pi.Warning)
179e5c31af7Sopenharmony_ci    logDiag('EXTRACT:', pi.extractPage)
180e5c31af7Sopenharmony_ci    logDiag('EMBED:  ', pi.embed)
181e5c31af7Sopenharmony_ci    logDiag('DESC:   ', pi.desc)
182e5c31af7Sopenharmony_ci    printPageInfoField('BEGIN   ', pi.begin,    file)
183e5c31af7Sopenharmony_ci    printPageInfoField('INCLUDE ', pi.include,  file)
184e5c31af7Sopenharmony_ci    printPageInfoField('PARAM   ', pi.param,    file)
185e5c31af7Sopenharmony_ci    printPageInfoField('BODY    ', pi.body,     file)
186e5c31af7Sopenharmony_ci    printPageInfoField('VALIDITY', pi.validity, file)
187e5c31af7Sopenharmony_ci    printPageInfoField('END     ', pi.end,      file)
188e5c31af7Sopenharmony_ci    logDiag('REFS: "' + pi.refs + '"')
189e5c31af7Sopenharmony_ci
190e5c31af7Sopenharmony_cidef prevPara(file, line):
191e5c31af7Sopenharmony_ci    """Go back one paragraph from the specified line and return the line number
192e5c31af7Sopenharmony_ci    of the first line of that paragraph.
193e5c31af7Sopenharmony_ci
194e5c31af7Sopenharmony_ci    Paragraphs are delimited by blank lines. It is assumed that the
195e5c31af7Sopenharmony_ci    current line is the first line of a paragraph.
196e5c31af7Sopenharmony_ci
197e5c31af7Sopenharmony_ci    - file is an array of strings
198e5c31af7Sopenharmony_ci    - line is the starting point (zero-based)"""
199e5c31af7Sopenharmony_ci    # Skip over current paragraph
200e5c31af7Sopenharmony_ci    while (line >= 0 and not isempty(file[line])):
201e5c31af7Sopenharmony_ci        line = line - 1
202e5c31af7Sopenharmony_ci    # Skip over white space
203e5c31af7Sopenharmony_ci    while (line >= 0 and isempty(file[line])):
204e5c31af7Sopenharmony_ci        line = line - 1
205e5c31af7Sopenharmony_ci    # Skip to first line of previous paragraph
206e5c31af7Sopenharmony_ci    while (line >= 1 and not isempty(file[line-1])):
207e5c31af7Sopenharmony_ci        line = line - 1
208e5c31af7Sopenharmony_ci    return line
209e5c31af7Sopenharmony_ci
210e5c31af7Sopenharmony_cidef nextPara(file, line):
211e5c31af7Sopenharmony_ci    """Go forward one paragraph from the specified line and return the line
212e5c31af7Sopenharmony_ci    number of the first line of that paragraph.
213e5c31af7Sopenharmony_ci
214e5c31af7Sopenharmony_ci    Paragraphs are delimited by blank lines. It is assumed that the
215e5c31af7Sopenharmony_ci    current line is standalone (which is bogus).
216e5c31af7Sopenharmony_ci
217e5c31af7Sopenharmony_ci    - file is an array of strings
218e5c31af7Sopenharmony_ci    - line is the starting point (zero-based)"""
219e5c31af7Sopenharmony_ci    maxLine = len(file) - 1
220e5c31af7Sopenharmony_ci    # Skip over current paragraph
221e5c31af7Sopenharmony_ci    while (line != maxLine and not isempty(file[line])):
222e5c31af7Sopenharmony_ci        line = line + 1
223e5c31af7Sopenharmony_ci    # Skip over white space
224e5c31af7Sopenharmony_ci    while (line != maxLine and isempty(file[line])):
225e5c31af7Sopenharmony_ci        line = line + 1
226e5c31af7Sopenharmony_ci    return line
227e5c31af7Sopenharmony_ci
228e5c31af7Sopenharmony_cidef lookupPage(pageMap, name):
229e5c31af7Sopenharmony_ci    """Return (creating if needed) the pageInfo entry in pageMap for name"""
230e5c31af7Sopenharmony_ci    if name not in pageMap:
231e5c31af7Sopenharmony_ci        pi = pageInfo()
232e5c31af7Sopenharmony_ci        pi.name = name
233e5c31af7Sopenharmony_ci        pageMap[name] = pi
234e5c31af7Sopenharmony_ci    else:
235e5c31af7Sopenharmony_ci        pi = pageMap[name]
236e5c31af7Sopenharmony_ci    return pi
237e5c31af7Sopenharmony_ci
238e5c31af7Sopenharmony_cidef loadFile(filename):
239e5c31af7Sopenharmony_ci    """Load a file into a list of strings. Return the (list, newline_string) or (None, None) on failure"""
240e5c31af7Sopenharmony_ci    newline_string = "\n"
241e5c31af7Sopenharmony_ci    try:
242e5c31af7Sopenharmony_ci        with open(filename, 'rb') as fp:
243e5c31af7Sopenharmony_ci            contents = fp.read()
244e5c31af7Sopenharmony_ci            if contents.count(b"\r\n") > 1:
245e5c31af7Sopenharmony_ci                newline_string = "\r\n"
246e5c31af7Sopenharmony_ci
247e5c31af7Sopenharmony_ci        with open(filename, 'r', encoding='utf-8') as fp:
248e5c31af7Sopenharmony_ci            lines = fp.readlines()
249e5c31af7Sopenharmony_ci    except:
250e5c31af7Sopenharmony_ci        logWarn('Cannot open file', filename, ':', sys.exc_info()[0])
251e5c31af7Sopenharmony_ci        return None, None
252e5c31af7Sopenharmony_ci
253e5c31af7Sopenharmony_ci    return lines, newline_string
254e5c31af7Sopenharmony_ci
255e5c31af7Sopenharmony_cidef clampToBlock(line, minline, maxline):
256e5c31af7Sopenharmony_ci    """Clamp a line number to be in the range [minline,maxline].
257e5c31af7Sopenharmony_ci
258e5c31af7Sopenharmony_ci    If the line number is None, just return it.
259e5c31af7Sopenharmony_ci    If minline is None, do not clamp to that value."""
260e5c31af7Sopenharmony_ci    if line is None:
261e5c31af7Sopenharmony_ci        return line
262e5c31af7Sopenharmony_ci    if minline and line < minline:
263e5c31af7Sopenharmony_ci        return minline
264e5c31af7Sopenharmony_ci    if line > maxline:
265e5c31af7Sopenharmony_ci        return maxline
266e5c31af7Sopenharmony_ci
267e5c31af7Sopenharmony_ci    return line
268e5c31af7Sopenharmony_ci
269e5c31af7Sopenharmony_cidef fixupRefs(pageMap, specFile, file):
270e5c31af7Sopenharmony_ci    """Fill in missing fields in pageInfo structures, to the extent they can be
271e5c31af7Sopenharmony_ci    inferred.
272e5c31af7Sopenharmony_ci
273e5c31af7Sopenharmony_ci    - pageMap - dictionary of pageInfo structures
274e5c31af7Sopenharmony_ci    - specFile - filename
275e5c31af7Sopenharmony_ci    - file - list of strings making up the file, indexed by pageInfo"""
276e5c31af7Sopenharmony_ci    # All potential ref pages are now in pageMap. Process them to
277e5c31af7Sopenharmony_ci    # identify actual page start/end/description boundaries, if
278e5c31af7Sopenharmony_ci    # not already determined from the text.
279e5c31af7Sopenharmony_ci    for name in sorted(pageMap.keys()):
280e5c31af7Sopenharmony_ci        pi = pageMap[name]
281e5c31af7Sopenharmony_ci
282e5c31af7Sopenharmony_ci        # # If nothing is found but an include line with no begin, validity,
283e5c31af7Sopenharmony_ci        # # or end, this is not intended as a ref page (yet). Set the begin
284e5c31af7Sopenharmony_ci        # # line to the include line, so autogeneration can at least
285e5c31af7Sopenharmony_ci        # # pull the include out, but mark it not to be extracted.
286e5c31af7Sopenharmony_ci        # # Examples include the host sync table includes in
287e5c31af7Sopenharmony_ci        # # chapters/fundamentals.adoc and the table of Vk*Flag types in
288e5c31af7Sopenharmony_ci        # # appendices/boilerplate.adoc.
289e5c31af7Sopenharmony_ci        # if pi.begin is None and pi.validity is None and pi.end is None:
290e5c31af7Sopenharmony_ci        #     pi.begin = pi.include
291e5c31af7Sopenharmony_ci        #     pi.extractPage = False
292e5c31af7Sopenharmony_ci        #     pi.Warning = 'No begin, validity, or end lines identified'
293e5c31af7Sopenharmony_ci        #     continue
294e5c31af7Sopenharmony_ci
295e5c31af7Sopenharmony_ci        # Using open block delimiters, ref pages must *always* have a
296e5c31af7Sopenharmony_ci        # defined begin and end. If either is undefined, that is fatal.
297e5c31af7Sopenharmony_ci        if pi.begin is None:
298e5c31af7Sopenharmony_ci            pi.extractPage = False
299e5c31af7Sopenharmony_ci            pi.Warning = 'Can\'t identify begin of ref page open block'
300e5c31af7Sopenharmony_ci            continue
301e5c31af7Sopenharmony_ci
302e5c31af7Sopenharmony_ci        if pi.end is None:
303e5c31af7Sopenharmony_ci            pi.extractPage = False
304e5c31af7Sopenharmony_ci            pi.Warning = 'Can\'t identify end of ref page open block'
305e5c31af7Sopenharmony_ci            continue
306e5c31af7Sopenharmony_ci
307e5c31af7Sopenharmony_ci        # If there is no description of the page, infer one from the type
308e5c31af7Sopenharmony_ci        if pi.desc is None:
309e5c31af7Sopenharmony_ci            if pi.type is not None:
310e5c31af7Sopenharmony_ci                # pi.desc = pi.type[0:len(pi.type)-1] + ' (no short description available)'
311e5c31af7Sopenharmony_ci                pi.Warning = 'No short description available; could infer from the type and name'
312e5c31af7Sopenharmony_ci            else:
313e5c31af7Sopenharmony_ci                pi.extractPage = False
314e5c31af7Sopenharmony_ci                pi.Warning = 'No short description available, cannot infer from the type'
315e5c31af7Sopenharmony_ci                continue
316e5c31af7Sopenharmony_ci
317e5c31af7Sopenharmony_ci        # Try to determine where the parameter and body sections of the page
318e5c31af7Sopenharmony_ci        # begin. funcpointer, proto, and struct pages infer the location of
319e5c31af7Sopenharmony_ci        # the parameter and body sections. Other pages infer the location of
320e5c31af7Sopenharmony_ci        # the body, but have no parameter sections.
321e5c31af7Sopenharmony_ci        #
322e5c31af7Sopenharmony_ci        # Probably some other types infer this as well - refer to list of
323e5c31af7Sopenharmony_ci        # all page types in genRef.py:emitPage()
324e5c31af7Sopenharmony_ci        if pi.include is not None:
325e5c31af7Sopenharmony_ci            if pi.type in ['funcpointers', 'protos', 'structs']:
326e5c31af7Sopenharmony_ci                pi.param = nextPara(file, pi.include)
327e5c31af7Sopenharmony_ci                if pi.body is None:
328e5c31af7Sopenharmony_ci                    pi.body = nextPara(file, pi.param)
329e5c31af7Sopenharmony_ci            else:
330e5c31af7Sopenharmony_ci                if pi.body is None:
331e5c31af7Sopenharmony_ci                    pi.body = nextPara(file, pi.include)
332e5c31af7Sopenharmony_ci        else:
333e5c31af7Sopenharmony_ci            pi.Warning = 'Page does not have an API definition include::'
334e5c31af7Sopenharmony_ci
335e5c31af7Sopenharmony_ci        # It is possible for the inferred param and body lines to run past
336e5c31af7Sopenharmony_ci        # the end of block, if, for example, there is no parameter section.
337e5c31af7Sopenharmony_ci        pi.param = clampToBlock(pi.param, pi.include, pi.end)
338e5c31af7Sopenharmony_ci        pi.body = clampToBlock(pi.body, pi.param, pi.end)
339e5c31af7Sopenharmony_ci
340e5c31af7Sopenharmony_ci        # We can get to this point with .include, .param, and .validity
341e5c31af7Sopenharmony_ci        # all being None, indicating those sections were not found.
342e5c31af7Sopenharmony_ci
343e5c31af7Sopenharmony_ci        logDiag('fixupRefs: after processing,', pi.name, 'looks like:')
344e5c31af7Sopenharmony_ci        printPageInfo(pi, file)
345e5c31af7Sopenharmony_ci
346e5c31af7Sopenharmony_ci    # Now that all the valid pages have been found, try to make some
347e5c31af7Sopenharmony_ci    # inferences about invalid pages.
348e5c31af7Sopenharmony_ci    #
349e5c31af7Sopenharmony_ci    # If a reference without a .end is entirely inside a valid reference,
350e5c31af7Sopenharmony_ci    # then it is intentionally embedded - may want to create an indirect
351e5c31af7Sopenharmony_ci    # page that links into the embedding page. This is done by a very
352e5c31af7Sopenharmony_ci    # inefficient double loop, but the loop depth is small.
353e5c31af7Sopenharmony_ci    for name in sorted(pageMap.keys()):
354e5c31af7Sopenharmony_ci        pi = pageMap[name]
355e5c31af7Sopenharmony_ci
356e5c31af7Sopenharmony_ci        if pi.end is None:
357e5c31af7Sopenharmony_ci            for embedName in sorted(pageMap.keys()):
358e5c31af7Sopenharmony_ci                logDiag('fixupRefs: comparing', pi.name, 'to', embedName)
359e5c31af7Sopenharmony_ci                embed = pageMap[embedName]
360e5c31af7Sopenharmony_ci                # Do not check embeddings which are themselves invalid
361e5c31af7Sopenharmony_ci                if not embed.extractPage:
362e5c31af7Sopenharmony_ci                    logDiag('Skipping check for embedding in:', embed.name)
363e5c31af7Sopenharmony_ci                    continue
364e5c31af7Sopenharmony_ci                if embed.begin is None or embed.end is None:
365e5c31af7Sopenharmony_ci                    logDiag('fixupRefs:', name + ':',
366e5c31af7Sopenharmony_ci                            'can\'t compare to unanchored ref:', embed.name,
367e5c31af7Sopenharmony_ci                            'in', specFile, 'at line', pi.include )
368e5c31af7Sopenharmony_ci                    printPageInfo(pi, file)
369e5c31af7Sopenharmony_ci                    printPageInfo(embed, file)
370e5c31af7Sopenharmony_ci                # If an embed is found, change the error to a warning
371e5c31af7Sopenharmony_ci                elif (pi.include is not None and pi.include >= embed.begin and
372e5c31af7Sopenharmony_ci                      pi.include <= embed.end):
373e5c31af7Sopenharmony_ci                    logDiag('fixupRefs: Found embed for:', name,
374e5c31af7Sopenharmony_ci                            'inside:', embedName,
375e5c31af7Sopenharmony_ci                            'in', specFile, 'at line', pi.include )
376e5c31af7Sopenharmony_ci                    pi.embed = embed.name
377e5c31af7Sopenharmony_ci                    pi.Warning = 'Embedded in definition for ' + embed.name
378e5c31af7Sopenharmony_ci                    break
379e5c31af7Sopenharmony_ci                else:
380e5c31af7Sopenharmony_ci                    logDiag('fixupRefs: No embed match for:', name,
381e5c31af7Sopenharmony_ci                            'inside:', embedName, 'in', specFile,
382e5c31af7Sopenharmony_ci                            'at line', pi.include)
383e5c31af7Sopenharmony_ci
384e5c31af7Sopenharmony_ci
385e5c31af7Sopenharmony_cidef compatiblePageTypes(refpage_type, pagemap_type):
386e5c31af7Sopenharmony_ci    """Returns whether two refpage 'types' (categories) are compatible -
387e5c31af7Sopenharmony_ci       this is only true for 'consts' and 'enums' types."""
388e5c31af7Sopenharmony_ci
389e5c31af7Sopenharmony_ci    constsEnums = [ 'consts', 'enums' ]
390e5c31af7Sopenharmony_ci
391e5c31af7Sopenharmony_ci    if refpage_type == pagemap_type:
392e5c31af7Sopenharmony_ci        return True
393e5c31af7Sopenharmony_ci    if refpage_type in constsEnums and pagemap_type in constsEnums:
394e5c31af7Sopenharmony_ci        return True
395e5c31af7Sopenharmony_ci    return False
396e5c31af7Sopenharmony_ci
397e5c31af7Sopenharmony_ci# Patterns used to recognize interesting lines in an asciidoc source file.
398e5c31af7Sopenharmony_ci# These patterns are only compiled once.
399e5c31af7Sopenharmony_ciendifPat   = re.compile(r'^endif::(?P<condition>[\w_+,]+)\[\]')
400e5c31af7Sopenharmony_cibeginPat   = re.compile(r'^\[open,(?P<attribs>refpage=.*)\]')
401e5c31af7Sopenharmony_ci# attribute key/value pairs of an open block
402e5c31af7Sopenharmony_ciattribStr  = r"([a-z]+)='([^'\\]*(?:\\.[^'\\]*)*)'"
403e5c31af7Sopenharmony_ciattribPat  = re.compile(attribStr)
404e5c31af7Sopenharmony_cibodyPat    = re.compile(r'^// *refBody')
405e5c31af7Sopenharmony_cierrorPat   = re.compile(r'^// *refError')
406e5c31af7Sopenharmony_ci
407e5c31af7Sopenharmony_ci# This regex transplanted from check_spec_links
408e5c31af7Sopenharmony_ci# It looks for various generated file conventions, and for the api/validity
409e5c31af7Sopenharmony_ci# include (generated_type), protos/struct/etc path (category), and API name
410e5c31af7Sopenharmony_ci# (entity_name).
411e5c31af7Sopenharmony_ci# It could be put into the API conventions object, instead of being
412e5c31af7Sopenharmony_ci# generalized for all the different specs.
413e5c31af7Sopenharmony_ciINCLUDE = re.compile(
414e5c31af7Sopenharmony_ci        r'include::(?P<directory_traverse>((../){1,4}|\{generated\}/)(generated/)?)(?P<generated_type>[\w]+)/(?P<category>\w+)/(?P<entity_name>[^./]+)\.(adoc|txt)[\[][\]]')
415e5c31af7Sopenharmony_ci
416e5c31af7Sopenharmony_cidef findRefs(file, filename):
417e5c31af7Sopenharmony_ci    """Identify reference pages in a list of strings, returning a dictionary of
418e5c31af7Sopenharmony_ci    pageInfo entries for each one found, or None on failure."""
419e5c31af7Sopenharmony_ci    setLogSourcefile(filename)
420e5c31af7Sopenharmony_ci    setLogProcname('findRefs')
421e5c31af7Sopenharmony_ci
422e5c31af7Sopenharmony_ci    # To reliably detect the open blocks around reference pages, we must
423e5c31af7Sopenharmony_ci    # first detect the '[open,refpage=...]' markup delimiting the block;
424e5c31af7Sopenharmony_ci    # skip past the '--' block delimiter on the next line; and identify the
425e5c31af7Sopenharmony_ci    # '--' block delimiter closing the page.
426e5c31af7Sopenharmony_ci    # This cannot be done solely with pattern matching, and requires state to
427e5c31af7Sopenharmony_ci    # track 'inside/outside block'.
428e5c31af7Sopenharmony_ci    # When looking for open blocks, possible states are:
429e5c31af7Sopenharmony_ci    #   'outside' - outside a block
430e5c31af7Sopenharmony_ci    #   'start' - have found the '[open...]' line
431e5c31af7Sopenharmony_ci    #   'inside' - have found the following '--' line
432e5c31af7Sopenharmony_ci    openBlockState = 'outside'
433e5c31af7Sopenharmony_ci
434e5c31af7Sopenharmony_ci    # Dictionary of interesting line numbers and strings related to an API
435e5c31af7Sopenharmony_ci    # name
436e5c31af7Sopenharmony_ci    pageMap = {}
437e5c31af7Sopenharmony_ci
438e5c31af7Sopenharmony_ci    numLines = len(file)
439e5c31af7Sopenharmony_ci    line = 0
440e5c31af7Sopenharmony_ci
441e5c31af7Sopenharmony_ci    # Track the pageInfo object corresponding to the current open block
442e5c31af7Sopenharmony_ci    pi = None
443e5c31af7Sopenharmony_ci
444e5c31af7Sopenharmony_ci    while (line < numLines):
445e5c31af7Sopenharmony_ci        setLogLine(line)
446e5c31af7Sopenharmony_ci
447e5c31af7Sopenharmony_ci        # Only one of the patterns can possibly match. Add it to
448e5c31af7Sopenharmony_ci        # the dictionary for that name.
449e5c31af7Sopenharmony_ci
450e5c31af7Sopenharmony_ci        # [open,refpage=...] starting a refpage block
451e5c31af7Sopenharmony_ci        matches = beginPat.search(file[line])
452e5c31af7Sopenharmony_ci        if matches is not None:
453e5c31af7Sopenharmony_ci            logDiag('Matched open block pattern')
454e5c31af7Sopenharmony_ci            attribs = matches.group('attribs')
455e5c31af7Sopenharmony_ci
456e5c31af7Sopenharmony_ci            # If the previous open block was not closed, raise an error
457e5c31af7Sopenharmony_ci            if openBlockState != 'outside':
458e5c31af7Sopenharmony_ci                logErr('Nested open block starting at line', line, 'of',
459e5c31af7Sopenharmony_ci                       filename)
460e5c31af7Sopenharmony_ci
461e5c31af7Sopenharmony_ci            openBlockState = 'start'
462e5c31af7Sopenharmony_ci
463e5c31af7Sopenharmony_ci            # Parse the block attributes
464e5c31af7Sopenharmony_ci            matches = attribPat.findall(attribs)
465e5c31af7Sopenharmony_ci
466e5c31af7Sopenharmony_ci            # Extract each attribute
467e5c31af7Sopenharmony_ci            name = None
468e5c31af7Sopenharmony_ci            desc = None
469e5c31af7Sopenharmony_ci            refpage_type = None
470e5c31af7Sopenharmony_ci            spec_type = None
471e5c31af7Sopenharmony_ci            anchor = None
472e5c31af7Sopenharmony_ci            alias = None
473e5c31af7Sopenharmony_ci            xrefs = None
474e5c31af7Sopenharmony_ci
475e5c31af7Sopenharmony_ci            for (key,value) in matches:
476e5c31af7Sopenharmony_ci                logDiag('got attribute', key, '=', value)
477e5c31af7Sopenharmony_ci                if key == 'refpage':
478e5c31af7Sopenharmony_ci                    name = value
479e5c31af7Sopenharmony_ci                elif key == 'desc':
480e5c31af7Sopenharmony_ci                    desc = unescapeQuotes(value)
481e5c31af7Sopenharmony_ci                elif key == 'type':
482e5c31af7Sopenharmony_ci                    refpage_type = value
483e5c31af7Sopenharmony_ci                elif key == 'spec':
484e5c31af7Sopenharmony_ci                    spec_type = value
485e5c31af7Sopenharmony_ci                elif key == 'anchor':
486e5c31af7Sopenharmony_ci                    anchor = value
487e5c31af7Sopenharmony_ci                elif key == 'alias':
488e5c31af7Sopenharmony_ci                    alias = value
489e5c31af7Sopenharmony_ci                elif key == 'xrefs':
490e5c31af7Sopenharmony_ci                    xrefs = value
491e5c31af7Sopenharmony_ci                else:
492e5c31af7Sopenharmony_ci                    logWarn('unknown open block attribute:', key)
493e5c31af7Sopenharmony_ci
494e5c31af7Sopenharmony_ci            if name is None or desc is None or refpage_type is None:
495e5c31af7Sopenharmony_ci                logWarn('missing one or more required open block attributes:'
496e5c31af7Sopenharmony_ci                        'refpage, desc, or type')
497e5c31af7Sopenharmony_ci                # Leave pi is None so open block delimiters are ignored
498e5c31af7Sopenharmony_ci            else:
499e5c31af7Sopenharmony_ci                pi = lookupPage(pageMap, name)
500e5c31af7Sopenharmony_ci                pi.desc = desc
501e5c31af7Sopenharmony_ci                # Must match later type definitions in interface/validity includes
502e5c31af7Sopenharmony_ci                pi.type = refpage_type
503e5c31af7Sopenharmony_ci                pi.spec = spec_type
504e5c31af7Sopenharmony_ci                pi.anchor = anchor
505e5c31af7Sopenharmony_ci                if alias:
506e5c31af7Sopenharmony_ci                    pi.alias = alias
507e5c31af7Sopenharmony_ci                if xrefs:
508e5c31af7Sopenharmony_ci                    pi.refs = xrefs
509e5c31af7Sopenharmony_ci                logDiag('open block for', name, 'added DESC =', desc,
510e5c31af7Sopenharmony_ci                        'TYPE =', refpage_type, 'ALIAS =', alias,
511e5c31af7Sopenharmony_ci                        'XREFS =', xrefs, 'SPEC =', spec_type,
512e5c31af7Sopenharmony_ci                        'ANCHOR =', anchor)
513e5c31af7Sopenharmony_ci
514e5c31af7Sopenharmony_ci            line = line + 1
515e5c31af7Sopenharmony_ci            continue
516e5c31af7Sopenharmony_ci
517e5c31af7Sopenharmony_ci        # '--' starting or ending and open block
518e5c31af7Sopenharmony_ci        if file[line].rstrip() == '--':
519e5c31af7Sopenharmony_ci            if openBlockState == 'outside':
520e5c31af7Sopenharmony_ci                # Only refpage open blocks should use -- delimiters
521e5c31af7Sopenharmony_ci                logWarn('Unexpected double-dash block delimiters')
522e5c31af7Sopenharmony_ci            elif openBlockState == 'start':
523e5c31af7Sopenharmony_ci                # -- delimiter following [open,refpage=...]
524e5c31af7Sopenharmony_ci                openBlockState = 'inside'
525e5c31af7Sopenharmony_ci
526e5c31af7Sopenharmony_ci                if pi is None:
527e5c31af7Sopenharmony_ci                    logWarn('no pageInfo available for opening -- delimiter')
528e5c31af7Sopenharmony_ci                else:
529e5c31af7Sopenharmony_ci                    pi.begin = line + 1
530e5c31af7Sopenharmony_ci                    logDiag('opening -- delimiter: added BEGIN =', pi.begin)
531e5c31af7Sopenharmony_ci            elif openBlockState == 'inside':
532e5c31af7Sopenharmony_ci                # -- delimiter ending an open block
533e5c31af7Sopenharmony_ci                if pi is None:
534e5c31af7Sopenharmony_ci                    logWarn('no pageInfo available for closing -- delimiter')
535e5c31af7Sopenharmony_ci                else:
536e5c31af7Sopenharmony_ci                    pi.end = line - 1
537e5c31af7Sopenharmony_ci                    logDiag('closing -- delimiter: added END =', pi.end)
538e5c31af7Sopenharmony_ci
539e5c31af7Sopenharmony_ci                openBlockState = 'outside'
540e5c31af7Sopenharmony_ci                pi = None
541e5c31af7Sopenharmony_ci            else:
542e5c31af7Sopenharmony_ci                logWarn('unknown openBlockState:', openBlockState)
543e5c31af7Sopenharmony_ci
544e5c31af7Sopenharmony_ci            line = line + 1
545e5c31af7Sopenharmony_ci            continue
546e5c31af7Sopenharmony_ci
547e5c31af7Sopenharmony_ci        matches = INCLUDE.search(file[line])
548e5c31af7Sopenharmony_ci        if matches is not None:
549e5c31af7Sopenharmony_ci            # Something got included, not sure what yet.
550e5c31af7Sopenharmony_ci            gen_type = matches.group('generated_type')
551e5c31af7Sopenharmony_ci            refpage_type = matches.group('category')
552e5c31af7Sopenharmony_ci            name = matches.group('entity_name')
553e5c31af7Sopenharmony_ci
554e5c31af7Sopenharmony_ci            # This will never match in OpenCL
555e5c31af7Sopenharmony_ci            if gen_type == 'validity':
556e5c31af7Sopenharmony_ci                logDiag('Matched validity pattern')
557e5c31af7Sopenharmony_ci                if pi is not None:
558e5c31af7Sopenharmony_ci                    if pi.type and not compatiblePageTypes(refpage_type, pi.type):
559e5c31af7Sopenharmony_ci                        logWarn('ERROR: pageMap[' + name + '] type:',
560e5c31af7Sopenharmony_ci                                pi.type, 'does not match type:', refpage_type)
561e5c31af7Sopenharmony_ci                    pi.type = refpage_type
562e5c31af7Sopenharmony_ci                    pi.validity = line
563e5c31af7Sopenharmony_ci                    logDiag('added TYPE =', pi.type, 'VALIDITY =', pi.validity)
564e5c31af7Sopenharmony_ci                else:
565e5c31af7Sopenharmony_ci                    logWarn('validity include:: line NOT inside block')
566e5c31af7Sopenharmony_ci
567e5c31af7Sopenharmony_ci                line = line + 1
568e5c31af7Sopenharmony_ci                continue
569e5c31af7Sopenharmony_ci
570e5c31af7Sopenharmony_ci            if gen_type == 'api':
571e5c31af7Sopenharmony_ci                logDiag('Matched include pattern')
572e5c31af7Sopenharmony_ci                if pi is not None:
573e5c31af7Sopenharmony_ci                    if pi.include is not None:
574e5c31af7Sopenharmony_ci                        logDiag('found multiple includes for this block')
575e5c31af7Sopenharmony_ci                    if pi.type and not compatiblePageTypes(refpage_type, pi.type):
576e5c31af7Sopenharmony_ci                        logWarn('ERROR: pageMap[' + name + '] type:',
577e5c31af7Sopenharmony_ci                                pi.type, 'does not match type:', refpage_type)
578e5c31af7Sopenharmony_ci                    pi.type = refpage_type
579e5c31af7Sopenharmony_ci                    pi.include = line
580e5c31af7Sopenharmony_ci                    logDiag('added TYPE =', pi.type, 'INCLUDE =', pi.include)
581e5c31af7Sopenharmony_ci                else:
582e5c31af7Sopenharmony_ci                    logWarn('interface include:: line NOT inside block')
583e5c31af7Sopenharmony_ci
584e5c31af7Sopenharmony_ci                line = line + 1
585e5c31af7Sopenharmony_ci                continue
586e5c31af7Sopenharmony_ci
587e5c31af7Sopenharmony_ci            logDiag('ignoring unrecognized include line ', matches.group())
588e5c31af7Sopenharmony_ci
589e5c31af7Sopenharmony_ci        # Vulkan 1.1 markup allows the last API include construct to be
590e5c31af7Sopenharmony_ci        # followed by an asciidoctor endif:: construct (and also preceded,
591e5c31af7Sopenharmony_ci        # at some distance).
592e5c31af7Sopenharmony_ci        # This looks for endif:: immediately following an include:: line
593e5c31af7Sopenharmony_ci        # and, if found, moves the include boundary to this line.
594e5c31af7Sopenharmony_ci        matches = endifPat.search(file[line])
595e5c31af7Sopenharmony_ci        if matches is not None and pi is not None:
596e5c31af7Sopenharmony_ci            if pi.include == line - 1:
597e5c31af7Sopenharmony_ci                logDiag('Matched endif pattern following include; moving include')
598e5c31af7Sopenharmony_ci                pi.include = line
599e5c31af7Sopenharmony_ci            else:
600e5c31af7Sopenharmony_ci                logDiag('Matched endif pattern (not following include)')
601e5c31af7Sopenharmony_ci
602e5c31af7Sopenharmony_ci            line = line + 1
603e5c31af7Sopenharmony_ci            continue
604e5c31af7Sopenharmony_ci
605e5c31af7Sopenharmony_ci        matches = bodyPat.search(file[line])
606e5c31af7Sopenharmony_ci        if matches is not None:
607e5c31af7Sopenharmony_ci            logDiag('Matched // refBody pattern')
608e5c31af7Sopenharmony_ci            if pi is not None:
609e5c31af7Sopenharmony_ci                pi.body = line
610e5c31af7Sopenharmony_ci                logDiag('added BODY =', pi.body)
611e5c31af7Sopenharmony_ci            else:
612e5c31af7Sopenharmony_ci                logWarn('// refBody line NOT inside block')
613e5c31af7Sopenharmony_ci
614e5c31af7Sopenharmony_ci            line = line + 1
615e5c31af7Sopenharmony_ci            continue
616e5c31af7Sopenharmony_ci
617e5c31af7Sopenharmony_ci        # OpenCL spec uses // refError to tag "validity" (Errors) language,
618e5c31af7Sopenharmony_ci        # instead of /validity/ includes.
619e5c31af7Sopenharmony_ci        matches = errorPat.search(file[line])
620e5c31af7Sopenharmony_ci        if matches is not None:
621e5c31af7Sopenharmony_ci            logDiag('Matched // refError pattern')
622e5c31af7Sopenharmony_ci            if pi is not None:
623e5c31af7Sopenharmony_ci                pi.validity = line
624e5c31af7Sopenharmony_ci                logDiag('added VALIDITY (refError) =', pi.validity)
625e5c31af7Sopenharmony_ci            else:
626e5c31af7Sopenharmony_ci                logWarn('// refError line NOT inside block')
627e5c31af7Sopenharmony_ci
628e5c31af7Sopenharmony_ci            line = line + 1
629e5c31af7Sopenharmony_ci            continue
630e5c31af7Sopenharmony_ci
631e5c31af7Sopenharmony_ci        line = line + 1
632e5c31af7Sopenharmony_ci        continue
633e5c31af7Sopenharmony_ci
634e5c31af7Sopenharmony_ci    if pi is not None:
635e5c31af7Sopenharmony_ci        logErr('Unclosed open block at EOF!')
636e5c31af7Sopenharmony_ci
637e5c31af7Sopenharmony_ci    setLogSourcefile(None)
638e5c31af7Sopenharmony_ci    setLogProcname(None)
639e5c31af7Sopenharmony_ci    setLogLine(None)
640e5c31af7Sopenharmony_ci
641e5c31af7Sopenharmony_ci    return pageMap
642e5c31af7Sopenharmony_ci
643e5c31af7Sopenharmony_ci
644e5c31af7Sopenharmony_cidef getBranch():
645e5c31af7Sopenharmony_ci    """Determine current git branch
646e5c31af7Sopenharmony_ci
647e5c31af7Sopenharmony_ci    Returns (branch name, ''), or (None, stderr output) if the branch name
648e5c31af7Sopenharmony_ci    cannot be determined"""
649e5c31af7Sopenharmony_ci
650e5c31af7Sopenharmony_ci    command = [ 'git', 'symbolic-ref', '--short', 'HEAD' ]
651e5c31af7Sopenharmony_ci    results = subprocess.run(command,
652e5c31af7Sopenharmony_ci                             stdout=subprocess.PIPE,
653e5c31af7Sopenharmony_ci                             stderr=subprocess.PIPE)
654e5c31af7Sopenharmony_ci
655e5c31af7Sopenharmony_ci    # git command failed
656e5c31af7Sopenharmony_ci    if len(results.stderr) > 0:
657e5c31af7Sopenharmony_ci        return (None, results.stderr)
658e5c31af7Sopenharmony_ci
659e5c31af7Sopenharmony_ci    # Remove newline from output and convert to a string
660e5c31af7Sopenharmony_ci    branch = results.stdout.rstrip().decode()
661e5c31af7Sopenharmony_ci    if len(branch) > 0:
662e5c31af7Sopenharmony_ci        # Strip trailing newline
663e5c31af7Sopenharmony_ci        branch = results.stdout.decode()[0:-1]
664e5c31af7Sopenharmony_ci
665e5c31af7Sopenharmony_ci    return (branch, '')
666