Vulkan-Docs/scripts/spec_tools/macro_checker.py

235 lines
9.0 KiB
Python
Raw Normal View History

Change log for February 3, 2019 Vulkan 1.1.99 spec update: * Update release number to 99. Public Issues: * Add missing pname:pMemoryHostPointerProperties description to flink:vkGetMemoryHostPointerPropertiesEXT.txt (public pull request 896). * Minor markup fixes (public pull request 900). * Minor update to `khronos.css` and markup fixes (originally proposed in public pull request 901, but done via an internal MR). Internal Issues: * Document restrictions on image queries for Y'CbCr formats in the <<features-formats-requiring-sampler-ycbcr-conversion>> table as well as for slink:sname:VkImageFormatProperties and slink:VkImageCreateInfo (internal issue 1361). * Correct type of the code:FragSizeEXT built-in in the <<interfaces-builtin-variables, Built-In Variables>> section (internal issue 1526). * Clean up math in the <<textures, Image Operations>> chapter by refactoring, using better naming conventions, updating diagrams to use the correct orientation, etc. (internal merge request 2968). * Fix minor typos for slink:VkImageCreateInfo and slink:VkImageStencilUsageCreateInfoEXT. * Add missing documentation for tlink:VkResolveModeFlagsKHR. * Fix extension dependency of pname:scalarBlockLayout in the <<features-features-requirements, Feature Requirements>> section. * Fix indexing math for shader binding table calculations in the <<shader-binding-table-indexing-rules, Indexing Rules>> section, and use spelling "`any-hit`" consistently. * Reconcile valid usage statement and text for sampled image layouts in slink:VkWriteDescriptorSet (https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/551). * Make SPIR-V code:OpConvertUToPtr and code:OpConvertPtrToU operations require a 64-bit integer for physical storage buffer pointers in the <<spirvenv-module-validation, Validation Rules within a Module>> section. * Update to KaTeX 10.0. New Extensions: * `VK_EXT_filter_cubic` * `VK_NV_dedicated_allocation_image_aliasing`
2019-02-04 01:26:23 -08:00
"""Provides the MacroChecker class."""
# Copyright (c) 2018-2019 Collabora, Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com>
from io import StringIO
import re
from .entity_db import EntityDatabase
from .shared import EXTENSION_CATEGORY
class MacroChecker(object):
"""Perform and track checking of one or more files in an API spec.
This does not necessarily need to be subclassed per-API: it is sufficiently
parameterized in the constructor for expected usage.
"""
def __init__(self, enabled_messages, entity_db,
macro_checker_file_type, root_path):
"""Construct an object that tracks checking one or more files in an API spec.
enabled_messages -- a set of MessageId that should be enabled.
entity_db -- an object of a EntityDatabase subclass for this API.
macro_checker_file_type -- Type to instantiate to create the right
MacroCheckerFile subclass for this API.
root_path -- A Path object for the root of this repository.
"""
self.enabled_messages = enabled_messages
self.entity_db = entity_db
self.macro_checker_file_type = macro_checker_file_type
self.root_path = root_path
self.files = []
self.refpages = set()
# keys: entity names. values: MessageContext
self.links = {}
self.apiIncludes = {}
self.validityIncludes = {}
self.headings = {}
# Regexes that are members because they depend on the name prefix.
# apiPrefix, followed by some word characters or * as many times as desired,
# NOT followed by >> and NOT preceded by one of the characters in that first character class.
# (which distinguish "names being used somewhere other than prose").
self.suspected_missing_macro_re = re.compile(
r'\b(?<![-=:/[\.`+,])(?P<entity_name>{}[\w*]+)\b(?!>>)'.format(
self.entity_db.case_insensitive_name_prefix_pattern)
)
self.heading_command_re = re.compile(
r'=+ (?P<command>{}[\w]+)'.format(self.entity_db.name_prefix)
)
macros_pattern = '|'.join([re.escape(macro)
for macro in self.entity_db.macros])
# the "formatting" group is to strip matching */**/_/__
# surrounding an entire macro.
self.macro_re = re.compile(
r'(?P<formatting>\**|_*)(?P<macro>{}):(?P<entity_name>[\w*]+((?P<subscript>[\[][^\]]*[\]]))?)(?P=formatting)'.format(macros_pattern))
def haveLinkTarget(self, entity):
"""Report if we have parsed an API include (or heading) for an entity.
None if there is no entity with that name.
"""
if not self.findEntity(entity):
return None
if entity in self.apiIncludes:
return True
return entity in self.headings
def hasFixes(self):
"""Report if any files have auto-fixes."""
for f in self.files:
if f.hasFixes():
return True
return False
def addLinkToEntity(self, entity, context):
"""Record seeing a link to an entity's docs from a context."""
if entity not in self.links:
self.links[entity] = []
self.links[entity].append(context)
def seenRefPage(self, entity):
"""Check if a ref-page markup block has been seen for an entity."""
return entity in self.refpages
def addRefPage(self, entity):
"""Record seeing a ref-page markup block for an entity."""
self.refpages.add(entity)
def findMacroAndEntity(self, macro, entity):
"""Look up EntityData by macro and entity pair.
Forwards to the EntityDatabase.
"""
return self.entity_db.findMacroAndEntity(macro, entity)
def findEntity(self, entity):
"""Look up EntityData by entity name (case-sensitive).
Forwards to the EntityDatabase.
"""
return self.entity_db.findEntity(entity)
def findEntityCaseInsensitive(self, entity):
"""Look up EntityData by entity name (case-insensitive).
Forwards to the EntityDatabase.
"""
return self.entity_db.findEntityCaseInsensitive(entity)
def getMemberNames(self, commandOrStruct):
"""Given a command or struct name, retrieve the names of each member/param.
Returns an empty list if the entity is not found or doesn't have members/params.
Forwards to the EntityDatabase.
"""
return self.entity_db.getMemberNames(commandOrStruct)
def likelyRecognizedEntity(self, entity_name):
"""Guess (based on name prefix alone) if an entity is likely to be recognized.
Forwards to the EntityDatabase.
"""
return self.entity_db.likelyRecognizedEntity(entity_name)
def isLinkedMacro(self, macro):
"""Identify if a macro is considered a "linked" macro.
Forwards to the EntityDatabase.
"""
return self.entity_db.isLinkedMacro(macro)
def processFile(self, filename):
"""Parse an .adoc file belonging to the spec and check it for errors."""
class FileStreamMaker(object):
def __init__(self, filename):
self.filename = filename
def make_stream(self):
return open(self.filename, 'r', encoding='utf-8')
f = self.macro_checker_file_type(self, filename, self.enabled_messages,
FileStreamMaker(filename))
f.process()
self.files.append(f)
def processString(self, s):
"""Process a string as if it were a spec file.
Used for testing purposes.
"""
if "\n" in s.rstrip():
# remove leading spaces from each line to allow easier
# block-quoting in tests
s = "\n".join([line.lstrip() for line in s.split("\n")])
# fabricate a "filename" that will display better.
filename = "string{}\n****START OF STRING****\n{}\n****END OF STRING****\n".format(
len(self.files), s.rstrip())
else:
filename = "string{}: {}".format(
len(self.files), s.rstrip())
class StringStreamMaker(object):
def __init__(self, string):
self.string = string
def make_stream(self):
return StringIO(self.string)
f = self.macro_checker_file_type(self, filename, self.enabled_messages,
StringStreamMaker(s))
f.process()
self.files.append(f)
return f
def numDiagnostics(self):
"""Return the total number of diagnostics (warnings and errors) over all the files processed."""
return sum([f.numDiagnostics() for f in self.files])
def numErrors(self):
"""Return the total number of errors over all the files processed."""
return sum([f.numErrors() for f in self.files])
def getMissingUnreferencedApiIncludes(self):
"""Return the unreferenced entity names that we expected to see an API include or link target for, but did not.
Counterpart to getBrokenLinks(): This method returns the entity names
that were not used in a linking macro (and thus wouldn't create a broken link),
but were nevertheless expected and not seen.
"""
return [entity for entity, _ in self.entity_db.generatingEntities.items()
if (not self.haveLinkTarget(entity)) and entity not in self.links]
def getBrokenLinks(self):
"""Return the entity names and usage contexts that we expected to see an API include or link target for, but did not.
Counterpart to getMissingUnreferencedApiIncludes(): This method returns only the
entity names that were used in a linking macro (and thus create a broken link),
but were not seen. The values of the dictionary are a list of MessageContext objects
for each linking macro usage for this entity name.
"""
return {entity: contexts for entity, contexts in self.links.items()
if entity in self.entity_db.generatingEntities and not self.haveLinkTarget(entity)}
def getMissingRefPages(self):
"""Return a list of entities that we expected, but did not see, a ref page block for.
The heuristics here are rather crude: we expect a ref page for every generating entry.
"""
missing = sorted([entity for entity, _ in self.entity_db.generatingEntities.items()
if entity not in self.refpages])
return missing