Move introspect to python/introspect
PiperOrigin-RevId: 728695024 Change-Id: I96433e1e9ee509704d66bd4c1be1906732e4cc55
This commit is contained in:
committed by
Copybara-Service
parent
6a86247810
commit
b0e9d08673
@@ -0,0 +1,298 @@
|
||||
# Copyright 2022 DeepMind Technologies Limited
|
||||
#
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Classes that roughly correspond to Clang AST node types."""
|
||||
|
||||
import collections
|
||||
import dataclasses
|
||||
import re
|
||||
from typing import Dict, Optional, Sequence, Tuple, Union
|
||||
|
||||
# We are relying on Clang to do the actual source parsing and are only doing
|
||||
# a little bit of extra parsing of function parameter type declarations here.
|
||||
# These patterns are here for sanity checking rather than actual parsing.
|
||||
VALID_TYPE_NAME_PATTERN = re.compile('(struct )?[A-Za-z_][A-Za-z0-9_]*')
|
||||
C_INVALID_TYPE_NAMES = frozenset([
|
||||
'auto', 'break', 'case', 'const', 'continue', 'default', 'do', 'else',
|
||||
'enum', 'extern', 'for', 'goto', 'if', 'inline', 'register', 'restrict',
|
||||
'return', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union',
|
||||
'volatile', 'while', '_Alignas', '_Atomic', '_Generic', '_Imaginary',
|
||||
'_Noreturn', '_Static_assert', '_Thread_local', '__attribute__', '_Pragma'])
|
||||
|
||||
|
||||
def _is_valid_integral_type(type_str: str):
|
||||
"""Checks if a string is a valid integral type."""
|
||||
parts = re.split(r'\s+', type_str)
|
||||
counter = collections.defaultdict(lambda: 0)
|
||||
wildcard_counter = 0
|
||||
for part in parts:
|
||||
if part in ('signed', 'unsigned', 'short', 'long', 'int', 'char'):
|
||||
counter[part] += 1
|
||||
elif VALID_TYPE_NAME_PATTERN.fullmatch(part):
|
||||
# a non-keyword can be a typedef for int
|
||||
wildcard_counter += 1
|
||||
else:
|
||||
return False
|
||||
|
||||
if (counter['signed'] + counter['unsigned'] > 1 or
|
||||
counter['short'] > 1 or counter['long'] > 2 or
|
||||
(counter['short'] and counter['long']) or
|
||||
((counter['short'] or counter['long']) and counter['char']) or
|
||||
counter['char'] + counter['int'] + wildcard_counter > 1):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ValueType:
|
||||
"""Represents a C type that is neither a pointer type nor an array type."""
|
||||
|
||||
name: str
|
||||
is_const: bool = False
|
||||
is_volatile: bool = False
|
||||
|
||||
def __init__(self, name: str, is_const: bool = False,
|
||||
is_volatile: bool = False):
|
||||
is_valid_type_name = (
|
||||
name == 'void *(*)(void *)' or
|
||||
VALID_TYPE_NAME_PATTERN.fullmatch(name) or
|
||||
_is_valid_integral_type(name)) and name not in C_INVALID_TYPE_NAMES
|
||||
if not is_valid_type_name:
|
||||
raise ValueError(f'{name!r} is not a valid value type name')
|
||||
self.name = name
|
||||
self.is_const = is_const
|
||||
self.is_volatile = is_volatile
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None) -> str:
|
||||
parts = []
|
||||
if self.is_const:
|
||||
parts.append('const')
|
||||
if self.is_volatile:
|
||||
parts.append('volatile')
|
||||
parts.append(self.name)
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
|
||||
def __str__(self):
|
||||
return self.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ArrayType:
|
||||
"""Represents a C array type."""
|
||||
|
||||
inner_type: Union[ValueType, 'PointerType']
|
||||
extents: Tuple[int, ...]
|
||||
|
||||
def __init__(self, inner_type: Union[ValueType, 'PointerType'],
|
||||
extents: Sequence[int]):
|
||||
self.inner_type = inner_type
|
||||
self.extents = tuple(extents)
|
||||
|
||||
@property
|
||||
def _extents_str(self) -> str:
|
||||
return ''.join(f'[{n}]' for n in self.extents)
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None) -> str:
|
||||
name_or_decl = name_or_decl or ''
|
||||
return self.inner_type.decl(f'{name_or_decl}{self._extents_str}')
|
||||
|
||||
def __str__(self):
|
||||
return self.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PointerType:
|
||||
"""Represents a C pointer type."""
|
||||
|
||||
inner_type: Union[ValueType, ArrayType, 'PointerType']
|
||||
is_const: bool = False
|
||||
is_volatile: bool = False
|
||||
is_restrict: bool = False
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None) -> str:
|
||||
"""Creates a string that declares an object of this type."""
|
||||
parts = ['*']
|
||||
if self.is_const:
|
||||
parts.append('const')
|
||||
if self.is_volatile:
|
||||
parts.append('volatile')
|
||||
if self.is_restrict:
|
||||
parts.append('restrict')
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
ptr_decl = ' '.join(parts)
|
||||
if isinstance(self.inner_type, ArrayType):
|
||||
ptr_decl = f'({ptr_decl})'
|
||||
return self.inner_type.decl(ptr_decl)
|
||||
|
||||
def __str__(self):
|
||||
return self.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FunctionParameterDecl:
|
||||
"""Represents a parameter in a function declaration.
|
||||
|
||||
Note that according to the C language rule, a function parameter of array
|
||||
type undergoes array-to-pointer decay, and therefore appears as a pointer
|
||||
parameter in an actual C AST. We retain the arrayness of a parameter here
|
||||
since the array's extents are informative.
|
||||
"""
|
||||
|
||||
name: str
|
||||
type: Union[ValueType, ArrayType, PointerType]
|
||||
|
||||
def __str__(self):
|
||||
return self.type.decl(self.name)
|
||||
|
||||
@property
|
||||
def decltype(self) -> str:
|
||||
return self.type.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FunctionDecl:
|
||||
"""Represents a function declaration."""
|
||||
|
||||
name: str
|
||||
return_type: Union[ValueType, ArrayType, PointerType]
|
||||
parameters: Tuple[FunctionParameterDecl, ...]
|
||||
doc: str
|
||||
|
||||
def __init__(self, name: str,
|
||||
return_type: Union[ValueType, ArrayType, PointerType],
|
||||
parameters: Sequence[FunctionParameterDecl],
|
||||
doc: str):
|
||||
self.name = name
|
||||
self.return_type = return_type
|
||||
self.parameters = tuple(parameters)
|
||||
self.doc = doc
|
||||
|
||||
def __str__(self):
|
||||
param_str = ', '.join(str(p) for p in self.parameters)
|
||||
return f'{self.return_type} {self.name}({param_str})'
|
||||
|
||||
@property
|
||||
def decltype(self) -> str:
|
||||
param_str = ', '.join(str(p.decltype) for p in self.parameters)
|
||||
return f'{self.return_type} ({param_str})'
|
||||
|
||||
|
||||
class _EnumDeclValues(Dict[str, int]):
|
||||
"""A dict with modified stringified representation.
|
||||
|
||||
The __repr__ method of this class adds a trailing comma to the list of values.
|
||||
This is done as a hint for code formatters to place one item per line when
|
||||
the stringified OrderedDict is used in generated Python code.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
out = super().__repr__()
|
||||
if self:
|
||||
out = re.sub(r'\(\[(.+)\]\)\Z', r'([\1,])', out)
|
||||
return re.sub(r'\A_EnumDeclValues', 'dict', out)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class EnumDecl:
|
||||
"""Represents an enum declaration."""
|
||||
|
||||
name: str
|
||||
declname: str
|
||||
values: Dict[str, int]
|
||||
|
||||
def __init__(self, name: str, declname: str, values: Dict[str, int]):
|
||||
self.name = name
|
||||
self.declname = declname
|
||||
self.values = _EnumDeclValues(values)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StructFieldDecl:
|
||||
"""Represents a field in a struct or union declaration."""
|
||||
|
||||
name: str
|
||||
type: Union[
|
||||
ValueType,
|
||||
ArrayType,
|
||||
PointerType,
|
||||
'AnonymousStructDecl',
|
||||
'AnonymousUnionDecl',
|
||||
]
|
||||
doc: str
|
||||
array_extent: Optional[Tuple[Union[str, int], ...]] = None
|
||||
|
||||
def __str__(self):
|
||||
return self.type.decl(self.name)
|
||||
|
||||
@property
|
||||
def decltype(self) -> str:
|
||||
return self.type.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class AnonymousStructDecl:
|
||||
"""Represents an anonymous struct declaration."""
|
||||
|
||||
fields: Tuple[Union[StructFieldDecl, 'AnonymousUnionDecl'], ...]
|
||||
|
||||
def __init__(self, fields: Sequence[StructFieldDecl]):
|
||||
self.fields = tuple(fields)
|
||||
|
||||
def __str__(self):
|
||||
return self.decl()
|
||||
|
||||
def _inner_decl(self):
|
||||
return '; '.join(str(field) for field in self.fields) + ';'
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None):
|
||||
parts = ['struct', f'{{{self._inner_decl()}}}']
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
class AnonymousUnionDecl(AnonymousStructDecl):
|
||||
"""Represents an anonymous union declaration."""
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None):
|
||||
parts = ['union', f'{{{self._inner_decl()}}}']
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StructDecl:
|
||||
"""Represents a struct declaration."""
|
||||
|
||||
name: str
|
||||
declname: str
|
||||
fields: Tuple[Union[StructFieldDecl, AnonymousUnionDecl], ...]
|
||||
|
||||
def __init__(self, name: str,
|
||||
declname: str,
|
||||
fields: Sequence[Union[StructFieldDecl, AnonymousUnionDecl]]):
|
||||
self.name = name
|
||||
self.declname = declname
|
||||
self.fields = tuple(fields)
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None) -> str:
|
||||
parts = [self.name]
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
Reference in New Issue
Block a user