summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/spec_tools/shared.py
diff options
context:
space:
mode:
Diffstat (limited to 'codegen/vulkan/scripts/spec_tools/shared.py')
-rw-r--r--codegen/vulkan/scripts/spec_tools/shared.py257
1 files changed, 257 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/spec_tools/shared.py b/codegen/vulkan/scripts/spec_tools/shared.py
new file mode 100644
index 00000000..bb6f1657
--- /dev/null
+++ b/codegen/vulkan/scripts/spec_tools/shared.py
@@ -0,0 +1,257 @@
+"""Types, constants, and utility functions used by multiple sub-modules in spec_tools."""
+
+# Copyright (c) 2018-2019 Collabora, Ltd.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com>
+
+import platform
+from collections import namedtuple
+from enum import Enum
+from inspect import getframeinfo
+from pathlib import Path
+from sys import stdout
+
+# if we have termcolor and we know our stdout is a TTY,
+# pull it in and use it.
+if hasattr(stdout, 'isatty') and stdout.isatty():
+ try:
+ from termcolor import colored as colored_impl
+ HAVE_COLOR = True
+ except ImportError:
+ HAVE_COLOR = False
+elif platform.system() == 'Windows':
+ try:
+ from termcolor import colored as colored_impl
+ import colorama
+ colorama.init()
+ HAVE_COLOR = True
+ except ImportError:
+ HAVE_COLOR = False
+
+else:
+ HAVE_COLOR = False
+
+
+def colored(s, color=None, attrs=None):
+ """Call termcolor.colored with same arguments if this is a tty and it is available."""
+ if HAVE_COLOR:
+ return colored_impl(s, color, attrs=attrs)
+ return s
+
+
+###
+# Constants used in multiple places.
+AUTO_FIX_STRING = 'Note: Auto-fix available.'
+EXTENSION_CATEGORY = 'extension'
+CATEGORIES_WITH_VALIDITY = set(('protos', 'structs'))
+NON_EXISTENT_MACROS = set(('plink', 'ttext', 'dtext'))
+
+###
+# MessageContext: All the information about where a message relates to.
+MessageContext = namedtuple('MessageContext',
+ ['filename', 'lineNum', 'line',
+ 'match', 'group'])
+
+
+def getInterestedRange(message_context):
+ """Return a (start, end) pair of character index for the match in a MessageContext."""
+ if not message_context.match:
+ # whole line
+ return (0, len(message_context.line))
+ return (message_context.match.start(), message_context.match.end())
+
+
+def getHighlightedRange(message_context):
+ """Return a (start, end) pair of character index for the highlighted range in a MessageContext."""
+ if message_context.group is not None and message_context.match is not None:
+ return (message_context.match.start(message_context.group),
+ message_context.match.end(message_context.group))
+ # no group (whole match) or no match (whole line)
+ return getInterestedRange(message_context)
+
+
+def toNameAndLine(context, root_path=None):
+ """Convert MessageContext into a simple filename:line string."""
+ my_fn = Path(context.filename)
+ if root_path:
+ my_fn = my_fn.relative_to(root_path)
+ return '{}:{}'.format(str(my_fn), context.lineNum)
+
+
+def generateInclude(dir_traverse, generated_type, category, entity):
+ """Create an include:: directive for geneated api or validity from the various pieces."""
+ return 'include::{directory_traverse}{generated_type}/{category}/{entity_name}.txt[]'.format(
+ directory_traverse=dir_traverse,
+ generated_type=generated_type,
+ category=category,
+ entity_name=entity)
+
+
+# Data stored per entity (function, struct, enumerant type, enumerant, extension, etc.)
+EntityData = namedtuple(
+ 'EntityData', ['entity', 'macro', 'elem', 'filename', 'category', 'directory'])
+
+
+class MessageType(Enum):
+ """Type of a message."""
+
+ WARNING = 1
+ ERROR = 2
+ NOTE = 3
+
+ def __str__(self):
+ """Format a MessageType as a lowercase string."""
+ return str(self.name).lower()
+
+ def formattedWithColon(self):
+ """Format a MessageType as a colored, lowercase string followed by a colon."""
+ if self == MessageType.WARNING:
+ return colored(str(self) + ':', 'magenta', attrs=['bold'])
+ if self == MessageType.ERROR:
+ return colored(str(self) + ':', 'red', attrs=['bold'])
+ return str(self) + ':'
+
+
+class MessageId(Enum):
+ """Enumerates the varieties of messages that can be generated.
+
+ Control over enabled messages with -Wbla or -Wno_bla is per-MessageId.
+ """
+
+ MISSING_TEXT = 1
+ LEGACY = 2
+ WRONG_MACRO = 3
+ MISSING_MACRO = 4
+ BAD_ENTITY = 5
+ BAD_ENUMERANT = 6
+ BAD_MACRO = 7
+ UNRECOGNIZED_CONTEXT = 8
+ UNKNOWN_MEMBER = 9
+ DUPLICATE_INCLUDE = 10
+ UNKNOWN_INCLUDE = 11
+ API_VALIDITY_ORDER = 12
+ UNDOCUMENTED_MEMBER = 13
+ MEMBER_PNAME_MISSING = 14
+ MISSING_VALIDITY_INCLUDE = 15
+ MISSING_API_INCLUDE = 16
+ MISUSED_TEXT = 17
+ EXTENSION = 18
+ REFPAGE_TAG = 19
+ REFPAGE_MISSING_DESC = 20
+ REFPAGE_XREFS = 21
+ REFPAGE_XREFS_COMMA = 22
+ REFPAGE_TYPE = 23
+ REFPAGE_NAME = 24
+ REFPAGE_BLOCK = 25
+ REFPAGE_MISSING = 26
+ REFPAGE_MISMATCH = 27
+ REFPAGE_UNKNOWN_ATTRIB = 28
+ REFPAGE_SELF_XREF = 29
+ REFPAGE_XREF_DUPE = 30
+ REFPAGE_WHITESPACE = 31
+ REFPAGE_DUPLICATE = 32
+ UNCLOSED_BLOCK = 33
+
+ def __str__(self):
+ """Format as a lowercase string."""
+ return self.name.lower()
+
+ def enable_arg(self):
+ """Return the corresponding Wbla string to make the 'enable this message' argument."""
+ return 'W{}'.format(self.name.lower())
+
+ def disable_arg(self):
+ """Return the corresponding Wno_bla string to make the 'enable this message' argument."""
+ return 'Wno_{}'.format(self.name.lower())
+
+ def desc(self):
+ """Return a brief description of the MessageId suitable for use in --help."""
+ return MessageId.DESCRIPTIONS[self]
+
+
+MessageId.DESCRIPTIONS = {
+ MessageId.MISSING_TEXT: "a *text: macro is expected but not found",
+ MessageId.LEGACY: "legacy usage of *name: macro when *link: is applicable",
+ MessageId.WRONG_MACRO: "wrong macro used for an entity",
+ MessageId.MISSING_MACRO: "a macro might be missing",
+ MessageId.BAD_ENTITY: "entity not recognized, etc.",
+ MessageId.BAD_ENUMERANT: "unrecognized enumerant value used in ename:",
+ MessageId.BAD_MACRO: "unrecognized macro used",
+ MessageId.UNRECOGNIZED_CONTEXT: "pname used with an unrecognized context",
+ MessageId.UNKNOWN_MEMBER: "pname used but member/argument by that name not found",
+ MessageId.DUPLICATE_INCLUDE: "duplicated include line",
+ MessageId.UNKNOWN_INCLUDE: "include line specified file we wouldn't expect to exists",
+ MessageId.API_VALIDITY_ORDER: "saw API include after validity include",
+ MessageId.UNDOCUMENTED_MEMBER: "saw an apparent struct/function documentation, but missing a member",
+ MessageId.MEMBER_PNAME_MISSING: "pname: missing from a 'scope' operator",
+ MessageId.MISSING_VALIDITY_INCLUDE: "missing validity include",
+ MessageId.MISSING_API_INCLUDE: "missing API include",
+ MessageId.MISUSED_TEXT: "a *text: macro is found but not expected",
+ MessageId.EXTENSION: "an extension name is incorrectly marked",
+ MessageId.REFPAGE_TAG: "a refpage tag is missing an expected field",
+ MessageId.REFPAGE_MISSING_DESC: "a refpage tag has an empty description",
+ MessageId.REFPAGE_XREFS: "an unrecognized entity is mentioned in xrefs of a refpage tag",
+ MessageId.REFPAGE_XREFS_COMMA: "a comma was founds in xrefs of a refpage tag, which is space-delimited",
+ MessageId.REFPAGE_TYPE: "a refpage tag has an incorrect type field",
+ MessageId.REFPAGE_NAME: "a refpage tag has an unrecognized entity name in its refpage field",
+ MessageId.REFPAGE_BLOCK: "a refpage block is not correctly opened or closed.",
+ MessageId.REFPAGE_MISSING: "an API include was found outside of a refpage block.",
+ MessageId.REFPAGE_MISMATCH: "an API or validity include was found in a non-matching refpage block.",
+ MessageId.REFPAGE_UNKNOWN_ATTRIB: "a refpage tag has an unrecognized attribute",
+ MessageId.REFPAGE_SELF_XREF: "a refpage tag has itself in the list of cross-references",
+ MessageId.REFPAGE_XREF_DUPE: "a refpage cross-references list has at least one duplicate",
+ MessageId.REFPAGE_WHITESPACE: "a refpage cross-references list has non-minimal whitespace",
+ MessageId.REFPAGE_DUPLICATE: "a refpage tag has been seen for a single entity for a second time",
+ MessageId.UNCLOSED_BLOCK: "one or more blocks remain unclosed at the end of a file"
+}
+
+
+class Message(object):
+ """An Error, Warning, or Note with a MessageContext, MessageId, and message text.
+
+ May optionally have a replacement, a see_also array, an auto-fix,
+ and a stack frame where the message was created.
+ """
+
+ def __init__(self, message_id, message_type, message, context,
+ replacement=None, see_also=None, fix=None, frame=None):
+ """Construct a Message.
+
+ Typically called by MacroCheckerFile.diag().
+ """
+ self.message_id = message_id
+
+ self.message_type = message_type
+
+ if isinstance(message, str):
+ self.message = [message]
+ else:
+ self.message = message
+
+ self.context = context
+ if context is not None and context.match is not None and context.group is not None:
+ if context.group not in context.match.groupdict():
+ raise RuntimeError(
+ 'Group "{}" does not exist in the match'.format(context.group))
+
+ self.replacement = replacement
+
+ self.fix = fix
+
+ if see_also is None:
+ self.see_also = None
+ elif isinstance(see_also, MessageContext):
+ self.see_also = [see_also]
+ else:
+ self.see_also = see_also
+
+ self.script_location = None
+ if frame:
+ try:
+ frameinfo = getframeinfo(frame)
+ self.script_location = "{}:{}".format(
+ frameinfo.filename, frameinfo.lineno)
+ finally:
+ del frame