diff options
Diffstat (limited to 'codegen/vulkan/scripts/reflib.py')
-rw-r--r-- | codegen/vulkan/scripts/reflib.py | 666 |
1 files changed, 0 insertions, 666 deletions
diff --git a/codegen/vulkan/scripts/reflib.py b/codegen/vulkan/scripts/reflib.py deleted file mode 100644 index bab7d30e..00000000 --- a/codegen/vulkan/scripts/reflib.py +++ /dev/null @@ -1,666 +0,0 @@ -#!/usr/bin/python3 -# -# Copyright 2016-2021 The Khronos Group Inc. -# -# SPDX-License-Identifier: Apache-2.0 - -# Utility functions for automatic ref page generation and other script stuff - -import io -import re -import sys -import subprocess - -# global errFile, warnFile, diagFile - -errFile = sys.stderr -warnFile = sys.stdout -diagFile = None -logSourcefile = None -logProcname = None -logLine = None - -def unescapeQuotes(s): - """Remove \' escape sequences in a string (refpage description)""" - return s.replace('\\\'', '\'') - -def write(*args, **kwargs ): - file = kwargs.pop('file',sys.stdout) - end = kwargs.pop('end','\n') - file.write(' '.join(str(arg) for arg in args)) - file.write(end) - -def setLogSourcefile(filename): - """Metadata which may be printed (if not None) for diagnostic messages""" - global logSourcefile - logSourcefile = filename - -def setLogProcname(procname): - global logProcname - logProcname = procname - -def setLogLine(line): - global logLine - logLine = line - -def logHeader(severity): - """Generate prefix for a diagnostic line using metadata and severity""" - global logSourcefile, logProcname, logLine - - msg = severity + ': ' - if logProcname: - msg = msg + ' in ' + logProcname - if logSourcefile: - msg = msg + ' for ' + logSourcefile - if logLine: - msg = msg + ' line ' + str(logLine) - return msg + ' ' - -def setLogFile(setDiag, setWarn, filename): - """Set the file handle to log either or both warnings and diagnostics to. - - - setDiag and setWarn are True if the corresponding handle is to be set. - - filename is None for no logging, '-' for stdout, or a pathname.""" - global diagFile, warnFile - - if filename is None: - return - - if filename == '-': - fp = sys.stdout - else: - fp = open(filename, 'w', encoding='utf-8') - - if setDiag: - diagFile = fp - if setWarn: - warnFile = fp - -def logDiag(*args, **kwargs): - file = kwargs.pop('file', diagFile) - end = kwargs.pop('end','\n') - if file is not None: - file.write(logHeader('DIAG') + ' '.join(str(arg) for arg in args)) - file.write(end) - -def logWarn(*args, **kwargs): - file = kwargs.pop('file', warnFile) - end = kwargs.pop('end','\n') - if file is not None: - file.write(logHeader('WARN') + ' '.join(str(arg) for arg in args)) - file.write(end) - -def logErr(*args, **kwargs): - file = kwargs.pop('file', errFile) - end = kwargs.pop('end','\n') - - strfile = io.StringIO() - strfile.write(logHeader('ERROR') + ' '.join(str(arg) for arg in args)) - strfile.write(end) - - if file is not None: - file.write(strfile.getvalue()) - sys.exit(1) - -def isempty(s): - """Return True if s is nothing but white space, False otherwise""" - return len(''.join(s.split())) == 0 - -class pageInfo: - """Information about a ref page relative to the file it's extracted from.""" - def __init__(self): - self.extractPage = True - """True if page should be extracted""" - - self.Warning = None - """string warning if page is suboptimal or can't be generated""" - - self.embed = False - """False or the name of the ref page this include is embedded within""" - - self.type = None - """refpage type attribute - 'structs', 'protos', 'freeform', etc.""" - - self.name = None - """struct/proto/enumerant/etc. name""" - - self.desc = None - """short description of ref page""" - - self.begin = None - """index of first line of the page (heuristic or // refBegin)""" - - self.include = None - """index of include:: line defining the page""" - - self.param = None - """index of first line of parameter/member definitions""" - - self.body = None - """index of first line of body text""" - - self.validity = None - """index of validity include""" - - self.end = None - """index of last line of the page (heuristic validity include, or // refEnd)""" - - self.alias = '' - """aliases of this name, if supplied, or ''""" - - self.refs = '' - """cross-references on // refEnd line, if supplied""" - - self.spec = None - """'spec' attribute in refpage open block, if supplied, or None for the default ('api') type""" - - self.anchor = None - """'anchor' attribute in refpage open block, if supplied, or inferred to be the same as the 'name'""" - -def printPageInfoField(desc, line, file): - """Print a single field of a pageInfo struct, possibly None. - - - desc - string description of field - - line - field value or None - - file - indexed by line""" - if line is not None: - logDiag(desc + ':', line + 1, '\t-> ', file[line], end='') - else: - logDiag(desc + ':', line) - -def printPageInfo(pi, file): - """Print out fields of a pageInfo struct - - - pi - pageInfo - - file - indexed by pageInfo""" - logDiag('TYPE: ', pi.type) - logDiag('NAME: ', pi.name) - logDiag('WARNING:', pi.Warning) - logDiag('EXTRACT:', pi.extractPage) - logDiag('EMBED: ', pi.embed) - logDiag('DESC: ', pi.desc) - printPageInfoField('BEGIN ', pi.begin, file) - printPageInfoField('INCLUDE ', pi.include, file) - printPageInfoField('PARAM ', pi.param, file) - printPageInfoField('BODY ', pi.body, file) - printPageInfoField('VALIDITY', pi.validity, file) - printPageInfoField('END ', pi.end, file) - logDiag('REFS: "' + pi.refs + '"') - -def prevPara(file, line): - """Go back one paragraph from the specified line and return the line number - of the first line of that paragraph. - - Paragraphs are delimited by blank lines. It is assumed that the - current line is the first line of a paragraph. - - - file is an array of strings - - line is the starting point (zero-based)""" - # Skip over current paragraph - while (line >= 0 and not isempty(file[line])): - line = line - 1 - # Skip over white space - while (line >= 0 and isempty(file[line])): - line = line - 1 - # Skip to first line of previous paragraph - while (line >= 1 and not isempty(file[line-1])): - line = line - 1 - return line - -def nextPara(file, line): - """Go forward one paragraph from the specified line and return the line - number of the first line of that paragraph. - - Paragraphs are delimited by blank lines. It is assumed that the - current line is standalone (which is bogus). - - - file is an array of strings - - line is the starting point (zero-based)""" - maxLine = len(file) - 1 - # Skip over current paragraph - while (line != maxLine and not isempty(file[line])): - line = line + 1 - # Skip over white space - while (line != maxLine and isempty(file[line])): - line = line + 1 - return line - -def lookupPage(pageMap, name): - """Return (creating if needed) the pageInfo entry in pageMap for name""" - if name not in pageMap: - pi = pageInfo() - pi.name = name - pageMap[name] = pi - else: - pi = pageMap[name] - return pi - -def loadFile(filename): - """Load a file into a list of strings. Return the list or None on failure""" - try: - fp = open(filename, 'r', encoding='utf-8') - except: - logWarn('Cannot open file', filename, ':', sys.exc_info()[0]) - return None - - file = fp.readlines() - fp.close() - - return file - -def clampToBlock(line, minline, maxline): - """Clamp a line number to be in the range [minline,maxline]. - - If the line number is None, just return it. - If minline is None, don't clamp to that value.""" - if line is None: - return line - if minline and line < minline: - return minline - if line > maxline: - return maxline - - return line - -def fixupRefs(pageMap, specFile, file): - """Fill in missing fields in pageInfo structures, to the extent they can be - inferred. - - - pageMap - dictionary of pageInfo structures - - specFile - filename - - file - list of strings making up the file, indexed by pageInfo""" - # All potential ref pages are now in pageMap. Process them to - # identify actual page start/end/description boundaries, if - # not already determined from the text. - for name in sorted(pageMap.keys()): - pi = pageMap[name] - - # # If nothing is found but an include line with no begin, validity, - # # or end, this is not intended as a ref page (yet). Set the begin - # # line to the include line, so autogeneration can at least - # # pull the include out, but mark it not to be extracted. - # # Examples include the host sync table includes in - # # chapters/fundamentals.txt and the table of Vk*Flag types in - # # appendices/boilerplate.txt. - # if pi.begin is None and pi.validity is None and pi.end is None: - # pi.begin = pi.include - # pi.extractPage = False - # pi.Warning = 'No begin, validity, or end lines identified' - # continue - - # Using open block delimiters, ref pages must *always* have a - # defined begin and end. If either is undefined, that's fatal. - if pi.begin is None: - pi.extractPage = False - pi.Warning = 'Can\'t identify begin of ref page open block' - continue - - if pi.end is None: - pi.extractPage = False - pi.Warning = 'Can\'t identify end of ref page open block' - continue - - # If there's no description of the page, infer one from the type - if pi.desc is None: - if pi.type is not None: - # pi.desc = pi.type[0:len(pi.type)-1] + ' (no short description available)' - pi.Warning = 'No short description available; could infer from the type and name' - else: - pi.extractPage = False - pi.Warning = 'No short description available, cannot infer from the type' - continue - - # Try to determine where the parameter and body sections of the page - # begin. funcpointer, proto, and struct pages infer the location of - # the parameter and body sections. Other pages infer the location of - # the body, but have no parameter sections. - # - #@ Probably some other types infer this as well - refer to list of - #@ all page types in genRef.py:emitPage() - if pi.include is not None: - if pi.type in ['funcpointers', 'protos', 'structs']: - pi.param = nextPara(file, pi.include) - if pi.body is None: - pi.body = nextPara(file, pi.param) - else: - if pi.body is None: - pi.body = nextPara(file, pi.include) - else: - pi.Warning = 'Page does not have an API definition include::' - - # It's possible for the inferred param and body lines to run past - # the end of block, if, for example, there is no parameter section. - pi.param = clampToBlock(pi.param, pi.include, pi.end) - pi.body = clampToBlock(pi.body, pi.param, pi.end) - - # We can get to this point with .include, .param, and .validity - # all being None, indicating those sections weren't found. - - logDiag('fixupRefs: after processing,', pi.name, 'looks like:') - printPageInfo(pi, file) - - # Now that all the valid pages have been found, try to make some - # inferences about invalid pages. - # - # If a reference without a .end is entirely inside a valid reference, - # then it's intentionally embedded - may want to create an indirect - # page that links into the embedding page. This is done by a very - # inefficient double loop, but the loop depth is small. - for name in sorted(pageMap.keys()): - pi = pageMap[name] - - if pi.end is None: - for embedName in sorted(pageMap.keys()): - logDiag('fixupRefs: comparing', pi.name, 'to', embedName) - embed = pageMap[embedName] - # Don't check embeddings which are themselves invalid - if not embed.extractPage: - logDiag('Skipping check for embedding in:', embed.name) - continue - if embed.begin is None or embed.end is None: - logDiag('fixupRefs:', name + ':', - 'can\'t compare to unanchored ref:', embed.name, - 'in', specFile, 'at line', pi.include ) - printPageInfo(pi, file) - printPageInfo(embed, file) - # If an embed is found, change the error to a warning - elif (pi.include is not None and pi.include >= embed.begin and - pi.include <= embed.end): - logDiag('fixupRefs: Found embed for:', name, - 'inside:', embedName, - 'in', specFile, 'at line', pi.include ) - pi.embed = embed.name - pi.Warning = 'Embedded in definition for ' + embed.name - break - else: - logDiag('fixupRefs: No embed match for:', name, - 'inside:', embedName, 'in', specFile, - 'at line', pi.include) - - -# Patterns used to recognize interesting lines in an asciidoc source file. -# These patterns are only compiled once. -INCSVAR_DEF = re.compile(r':INCS-VAR: (?P<value>.*)') -endifPat = re.compile(r'^endif::(?P<condition>[\w_+,]+)\[\]') -beginPat = re.compile(r'^\[open,(?P<attribs>refpage=.*)\]') -# attribute key/value pairs of an open block -attribStr = r"([a-z]+)='([^'\\]*(?:\\.[^'\\]*)*)'" -attribPat = re.compile(attribStr) -bodyPat = re.compile(r'^// *refBody') -errorPat = re.compile(r'^// *refError') - -# This regex transplanted from check_spec_links -# It looks for either OpenXR or Vulkan generated file conventions, and for -# the api/validity include (generated_type), protos/struct/etc path -# (category), and API name (entity_name). It could be put into the API -# conventions object. -INCLUDE = re.compile( - r'include::(?P<directory_traverse>((../){1,4}|\{INCS-VAR\}/|\{generated\}/)(generated/)?)(?P<generated_type>[\w]+)/(?P<category>\w+)/(?P<entity_name>[^./]+).txt[\[][\]]') - - -def findRefs(file, filename): - """Identify reference pages in a list of strings, returning a dictionary of - pageInfo entries for each one found, or None on failure.""" - setLogSourcefile(filename) - setLogProcname('findRefs') - - # To reliably detect the open blocks around reference pages, we must - # first detect the '[open,refpage=...]' markup delimiting the block; - # skip past the '--' block delimiter on the next line; and identify the - # '--' block delimiter closing the page. - # This can't be done solely with pattern matching, and requires state to - # track 'inside/outside block'. - # When looking for open blocks, possible states are: - # 'outside' - outside a block - # 'start' - have found the '[open...]' line - # 'inside' - have found the following '--' line - openBlockState = 'outside' - - # Dictionary of interesting line numbers and strings related to an API - # name - pageMap = {} - - numLines = len(file) - line = 0 - - # Track the pageInfo object corresponding to the current open block - pi = None - incsvar = None - - while (line < numLines): - setLogLine(line) - - # Look for a file-wide definition - matches = INCSVAR_DEF.match(file[line]) - if matches: - incsvar = matches.group('value') - logDiag('Matched INCS-VAR definition:', incsvar) - - line = line + 1 - continue - - # Perform INCS-VAR substitution immediately. - if incsvar and '{INCS-VAR}' in file[line]: - newLine = file[line].replace('{INCS-VAR}', incsvar) - logDiag('PERFORMING SUBSTITUTION', file[line], '->', newLine) - file[line] = newLine - - # Only one of the patterns can possibly match. Add it to - # the dictionary for that name. - - # [open,refpage=...] starting a refpage block - matches = beginPat.search(file[line]) - if matches is not None: - logDiag('Matched open block pattern') - attribs = matches.group('attribs') - - # If the previous open block wasn't closed, raise an error - if openBlockState != 'outside': - logErr('Nested open block starting at line', line, 'of', - filename) - - openBlockState = 'start' - - # Parse the block attributes - matches = attribPat.findall(attribs) - - # Extract each attribute - name = None - desc = None - refpage_type = None - spec_type = None - anchor = None - alias = None - xrefs = None - - for (key,value) in matches: - logDiag('got attribute', key, '=', value) - if key == 'refpage': - name = value - elif key == 'desc': - desc = unescapeQuotes(value) - elif key == 'type': - refpage_type = value - elif key == 'spec': - spec_type = value - elif key == 'anchor': - anchor = value - elif key == 'alias': - alias = value - elif key == 'xrefs': - xrefs = value - else: - logWarn('unknown open block attribute:', key) - - if name is None or desc is None or refpage_type is None: - logWarn('missing one or more required open block attributes:' - 'refpage, desc, or type') - # Leave pi is None so open block delimiters are ignored - else: - pi = lookupPage(pageMap, name) - pi.desc = desc - # Must match later type definitions in interface/validity includes - pi.type = refpage_type - pi.spec = spec_type - pi.anchor = anchor - if alias: - pi.alias = alias - if xrefs: - pi.refs = xrefs - logDiag('open block for', name, 'added DESC =', desc, - 'TYPE =', refpage_type, 'ALIAS =', alias, - 'XREFS =', xrefs, 'SPEC =', spec_type, - 'ANCHOR =', anchor) - - line = line + 1 - continue - - # '--' starting or ending and open block - if file[line].rstrip() == '--': - if openBlockState == 'outside': - # Only refpage open blocks should use -- delimiters - logWarn('Unexpected double-dash block delimiters') - elif openBlockState == 'start': - # -- delimiter following [open,refpage=...] - openBlockState = 'inside' - - if pi is None: - logWarn('no pageInfo available for opening -- delimiter') - else: - pi.begin = line + 1 - logDiag('opening -- delimiter: added BEGIN =', pi.begin) - elif openBlockState == 'inside': - # -- delimiter ending an open block - if pi is None: - logWarn('no pageInfo available for closing -- delimiter') - else: - pi.end = line - 1 - logDiag('closing -- delimiter: added END =', pi.end) - - openBlockState = 'outside' - pi = None - else: - logWarn('unknown openBlockState:', openBlockState) - - line = line + 1 - continue - - matches = INCLUDE.search(file[line]) - if matches is not None: - # Something got included, not sure what yet. - gen_type = matches.group('generated_type') - refpage_type = matches.group('category') - name = matches.group('entity_name') - - # This will never match in OpenCL - if gen_type == 'validity': - logDiag('Matched validity pattern') - if pi is not None: - if pi.type and refpage_type != pi.type: - logWarn('ERROR: pageMap[' + name + '] type:', - pi.type, 'does not match type:', refpage_type) - pi.type = refpage_type - pi.validity = line - logDiag('added TYPE =', pi.type, 'VALIDITY =', pi.validity) - else: - logWarn('validity include:: line NOT inside block') - - line = line + 1 - continue - - if gen_type == 'api': - logDiag('Matched include pattern') - if pi is not None: - if pi.include is not None: - logDiag('found multiple includes for this block') - if pi.type and refpage_type != pi.type: - logWarn('ERROR: pageMap[' + name + '] type:', - pi.type, 'does not match type:', refpage_type) - pi.type = refpage_type - pi.include = line - logDiag('added TYPE =', pi.type, 'INCLUDE =', pi.include) - else: - logWarn('interface include:: line NOT inside block') - - line = line + 1 - continue - - logDiag('ignoring unrecognized include line ', matches.group()) - - # Vulkan 1.1 markup allows the last API include construct to be - # followed by an asciidoctor endif:: construct (and also preceded, - # at some distance). - # This looks for endif:: immediately following an include:: line - # and, if found, moves the include boundary to this line. - matches = endifPat.search(file[line]) - if matches is not None and pi is not None: - if pi.include == line - 1: - logDiag('Matched endif pattern following include; moving include') - pi.include = line - else: - logDiag('Matched endif pattern (not following include)') - - line = line + 1 - continue - - matches = bodyPat.search(file[line]) - if matches is not None: - logDiag('Matched // refBody pattern') - if pi is not None: - pi.body = line - logDiag('added BODY =', pi.body) - else: - logWarn('// refBody line NOT inside block') - - line = line + 1 - continue - - # OpenCL spec uses // refError to tag "validity" (Errors) language, - # instead of /validity/ includes. - matches = errorPat.search(file[line]) - if matches is not None: - logDiag('Matched // refError pattern') - if pi is not None: - pi.validity = line - logDiag('added VALIDITY (refError) =', pi.validity) - else: - logWarn('// refError line NOT inside block') - - line = line + 1 - continue - - line = line + 1 - continue - - if pi is not None: - logErr('Unclosed open block at EOF!') - - setLogSourcefile(None) - setLogProcname(None) - setLogLine(None) - - return pageMap - - -def getBranch(): - """Determine current git branch - - Returns (branch name, ''), or (None, stderr output) if the branch name - can't be determined""" - - command = [ 'git', 'symbolic-ref', '--short', 'HEAD' ] - results = subprocess.run(command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - # git command failed - if len(results.stderr) > 0: - return (None, results.stderr) - - # Remove newline from output and convert to a string - branch = results.stdout.rstrip().decode() - if len(branch) > 0: - # Strip trailing newline - branch = results.stdout.decode()[0:-1] - - return (branch, '') |