Version 2.1.2: Python bindings, OBJ assets support, bugfixes.
PiperOrigin-RevId: 434731612 Change-Id: I0cfda3e7a3d1c72036764986efc252ffa1b8c6b0
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
# 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('[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 = (
|
||||
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)
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Tests for ast_nodes.py."""
|
||||
|
||||
from absl.testing import absltest
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
|
||||
|
||||
class AstNodesTest(absltest.TestCase):
|
||||
|
||||
def test_value_type(self):
|
||||
value_type = ast_nodes.ValueType('int')
|
||||
self.assertEqual(str(value_type), 'int')
|
||||
self.assertEqual(value_type.decl('var'), 'int var')
|
||||
|
||||
const_value_type = ast_nodes.ValueType('double', is_const=True)
|
||||
self.assertEqual(str(const_value_type), 'const double')
|
||||
self.assertEqual(const_value_type.decl('var2'), 'const double var2')
|
||||
|
||||
def test_pointer_type(self):
|
||||
pointer_type = ast_nodes.PointerType(ast_nodes.ValueType('int'))
|
||||
self.assertEqual(str(pointer_type), 'int *')
|
||||
self.assertEqual(pointer_type.decl('var'), 'int * var')
|
||||
|
||||
const_pointer_type = ast_nodes.PointerType(
|
||||
ast_nodes.ValueType('double'), is_const=True)
|
||||
self.assertEqual(str(const_pointer_type), 'double * const')
|
||||
self.assertEqual(const_pointer_type.decl('var2'), 'double * const var2')
|
||||
|
||||
pointer_to_const_type = ast_nodes.PointerType(
|
||||
ast_nodes.ValueType('float', is_const=True))
|
||||
self.assertEqual(str(pointer_to_const_type), 'const float *')
|
||||
self.assertEqual(pointer_to_const_type.decl('var3'), 'const float * var3')
|
||||
|
||||
restrict_volatile_pointer_to_const_type = ast_nodes.PointerType(
|
||||
ast_nodes.ValueType('char', is_const=True),
|
||||
is_volatile=True, is_restrict=True)
|
||||
self.assertEqual(str(restrict_volatile_pointer_to_const_type),
|
||||
'const char * volatile restrict')
|
||||
self.assertEqual(
|
||||
restrict_volatile_pointer_to_const_type.decl('var4'),
|
||||
'const char * volatile restrict var4')
|
||||
|
||||
pointer_to_array_type = ast_nodes.PointerType(
|
||||
ast_nodes.ArrayType(ast_nodes.ValueType('long'), (3,)))
|
||||
self.assertEqual(str(pointer_to_array_type), 'long (*)[3]')
|
||||
self.assertEqual(pointer_to_array_type.decl('var5'), 'long (* var5)[3]')
|
||||
|
||||
const_pointer_to_array_type = ast_nodes.PointerType(
|
||||
ast_nodes.ArrayType(ast_nodes.ValueType('unsigned int'), (4,)),
|
||||
is_const=True)
|
||||
self.assertEqual(
|
||||
str(const_pointer_to_array_type), 'unsigned int (* const)[4]')
|
||||
self.assertEqual(
|
||||
const_pointer_to_array_type.decl('var6'),
|
||||
'unsigned int (* const var6)[4]')
|
||||
|
||||
def test_array_type(self):
|
||||
array_type = ast_nodes.ArrayType(ast_nodes.ValueType('int'), (4,))
|
||||
self.assertEqual(str(array_type), 'int [4]')
|
||||
self.assertEqual(array_type.decl('var'), 'int var[4]')
|
||||
|
||||
array_2d_type = ast_nodes.ArrayType(
|
||||
ast_nodes.ValueType('double', is_const=True), (2, 3))
|
||||
self.assertEqual(str(array_2d_type), 'const double [2][3]')
|
||||
self.assertEqual(array_2d_type.decl('var2'), 'const double var2[2][3]')
|
||||
|
||||
array_to_pointer_type = ast_nodes.ArrayType(
|
||||
ast_nodes.PointerType(ast_nodes.ValueType('char', is_const=True)), (5,))
|
||||
self.assertEqual(str(array_to_pointer_type), 'const char * [5]')
|
||||
self.assertEqual(array_to_pointer_type.decl('var3'), 'const char * var3[5]')
|
||||
|
||||
array_to_const_pointer_type = ast_nodes.ArrayType(
|
||||
ast_nodes.PointerType(ast_nodes.ValueType('float'), is_const=True),
|
||||
(7,))
|
||||
self.assertEqual(str(array_to_const_pointer_type), 'float * const [7]')
|
||||
self.assertEqual(
|
||||
array_to_const_pointer_type.decl('var4'), 'float * const var4[7]')
|
||||
|
||||
def test_complex_type(self):
|
||||
complex_type = ast_nodes.ArrayType(
|
||||
extents=[9],
|
||||
inner_type=ast_nodes.PointerType(
|
||||
ast_nodes.PointerType(
|
||||
is_const=True,
|
||||
inner_type=ast_nodes.ArrayType(
|
||||
extents=[7],
|
||||
inner_type=ast_nodes.PointerType(
|
||||
is_const=True,
|
||||
inner_type=ast_nodes.PointerType(
|
||||
ast_nodes.ArrayType(
|
||||
extents=(3, 4),
|
||||
inner_type=ast_nodes.ValueType(
|
||||
'unsigned int', is_const=True)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertEqual(str(complex_type),
|
||||
'const unsigned int (* * const (* const * [9])[7])[3][4]')
|
||||
self.assertEqual(
|
||||
complex_type.decl('var'),
|
||||
'const unsigned int (* * const (* const * var[9])[7])[3][4]')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
@@ -0,0 +1,149 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Utility for formatting AST node as Python code."""
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
INDENT_WIDTH = 4
|
||||
MAX_LINE_WIDTH = 80
|
||||
SIMPLE_TYPES = frozenset([int, float, str, bool, bytes, type(None)])
|
||||
|
||||
|
||||
def format_as_python_code(obj: Any) -> str:
|
||||
"""Formats an AST node object as well-indented Python code."""
|
||||
formatter = _Formatter()
|
||||
formatter.add(obj)
|
||||
return str(formatter)
|
||||
|
||||
|
||||
def _is_all_simple(seq: Iterable[Any]) -> bool:
|
||||
return all(type(obj) in SIMPLE_TYPES for obj in seq)
|
||||
|
||||
|
||||
class _Formatter:
|
||||
"""A helper for pretty-printing AST nodes as Python code."""
|
||||
|
||||
def __init__(self):
|
||||
self._line_prefix = ''
|
||||
self._lines = []
|
||||
self._add_to_last_line = False
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _indent(self, width: int = INDENT_WIDTH):
|
||||
self._line_prefix += ' ' * width
|
||||
yield
|
||||
self._line_prefix = self._line_prefix[:-width]
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _append_at_end(self, s):
|
||||
yield
|
||||
self._lines[-1] += s
|
||||
|
||||
def _add_line(self, line: str, no_break: bool = False):
|
||||
if self._add_to_last_line:
|
||||
self._lines[-1] += line
|
||||
else:
|
||||
self._lines.append(self._line_prefix + line)
|
||||
self._add_to_last_line = no_break
|
||||
|
||||
def _add_dict(self, obj: Mapping[Any, Any]):
|
||||
"""Adds a dict to the formatted output."""
|
||||
self._add_line('dict([')
|
||||
with self._indent():
|
||||
for k, v in obj.items():
|
||||
|
||||
# Try to fit everything into a single line first.
|
||||
if _is_all_simple((k, v)):
|
||||
single_line = f'({k!r}, {v!r}),'
|
||||
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
|
||||
self._add_line(single_line)
|
||||
continue
|
||||
|
||||
self._add_line(f"('{k}',")
|
||||
with self._append_at_end('),'):
|
||||
with self._indent(1):
|
||||
self.add(v)
|
||||
|
||||
self._add_line('])')
|
||||
|
||||
def _add_dataclass(self, obj: Any):
|
||||
"""Adds a dataclass object to the formatted output."""
|
||||
# Filter out default values.
|
||||
kv_pairs = []
|
||||
for k in dataclasses.fields(obj):
|
||||
v = getattr(obj, k.name)
|
||||
if v != k.default:
|
||||
kv_pairs.append((k, v))
|
||||
|
||||
# Try to fit everything into a single line first.
|
||||
if _is_all_simple(v for _, v in kv_pairs):
|
||||
single_line = ', '.join(f'{k.name}={v!r}' for k, v in kv_pairs)
|
||||
single_line = f'{obj.__class__.__name__}({single_line})'
|
||||
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
|
||||
self._add_line(single_line)
|
||||
return
|
||||
|
||||
self._add_line(obj.__class__.__name__ + '(')
|
||||
with self._indent():
|
||||
for k, v in kv_pairs:
|
||||
self._add_line(k.name + '=', no_break=True)
|
||||
with self._append_at_end(','):
|
||||
self.add(v)
|
||||
self._add_line(')')
|
||||
|
||||
def _add_sequence(self, obj: Sequence[Any]) -> None:
|
||||
"""Adds a sequence to the formatted output."""
|
||||
default_str = repr(obj)
|
||||
open_token, close_token = default_str[0], default_str[-1]
|
||||
# Try to fit everything into a single line first.
|
||||
if _is_all_simple(obj):
|
||||
single_line = (
|
||||
f"{open_token}{', '.join(repr(o) for o in obj)}{close_token}")
|
||||
if close_token == ')' and len(obj) == 1:
|
||||
single_line = f'{single_line[:-1]},)'
|
||||
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
|
||||
self._add_line(single_line)
|
||||
return
|
||||
|
||||
self._add_line(open_token)
|
||||
with self._indent():
|
||||
for v in obj:
|
||||
with self._append_at_end(','):
|
||||
self.add(v)
|
||||
self._add_line(close_token)
|
||||
|
||||
def add(self, obj: Any) -> None:
|
||||
"""Adds an object to the formatted output."""
|
||||
if _is_all_simple((obj,)):
|
||||
self._add_line(repr(obj))
|
||||
elif dataclasses.is_dataclass(obj):
|
||||
self._add_dataclass(obj)
|
||||
elif isinstance(obj, Mapping):
|
||||
self._add_dict(obj)
|
||||
elif isinstance(obj, Sequence):
|
||||
self._add_sequence(obj)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
lines = []
|
||||
for line in self._lines:
|
||||
if len(line) > MAX_LINE_WIDTH:
|
||||
lines.append(f'{line} # pylint: disable=line-too-long')
|
||||
else:
|
||||
lines.append(line)
|
||||
return '\n'.join(lines)
|
||||
@@ -0,0 +1,126 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Generates enums.py.
|
||||
|
||||
The JSON input can be generated via:
|
||||
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments mujoco.h
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
from google3.third_party.mujoco.introspect.codegen import formatter
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string(
|
||||
'json_path', None,
|
||||
'Path to the JSON file representing the Clang AST for mujoco.h')
|
||||
|
||||
ClangJsonNode = Mapping[str, Any]
|
||||
|
||||
|
||||
def traverse(node, visitor):
|
||||
visitor.visit(node)
|
||||
children = node.get('inner', [])
|
||||
for child in children:
|
||||
traverse(child, visitor)
|
||||
|
||||
|
||||
class MjEnumVisitor:
|
||||
"""A Clang AST JSON node visitor for MuJoCo API enum declarations."""
|
||||
|
||||
def __init__(self):
|
||||
self._enums = {}
|
||||
self._typedefs = {}
|
||||
|
||||
def _make_enum(self, node: ClangJsonNode) -> ast_nodes.EnumDecl:
|
||||
"""Makes a EnumDecl from a Clang AST EnumDecl node."""
|
||||
name = f"enum {node['name']}"
|
||||
values = []
|
||||
for child in node['inner']:
|
||||
child_kind = child.get('kind')
|
||||
if child_kind == 'EnumConstantDecl':
|
||||
next_idx = values[-1][1] + 1 if values else 0
|
||||
value = int(child['inner'][0].get('value', next_idx))
|
||||
values.append((child['name'], value))
|
||||
return ast_nodes.EnumDecl(name=name, declname=name, values=dict(values))
|
||||
|
||||
def visit(self, node: ClangJsonNode) -> None:
|
||||
if (node.get('kind') == 'EnumDecl' and
|
||||
node.get('name', '').startswith('mj')):
|
||||
enum_decl = self._make_enum(node)
|
||||
self._enums[enum_decl.name] = enum_decl
|
||||
elif (node.get('kind') == 'TypedefDecl' and
|
||||
node['type']['qualType'].startswith('enum mj')):
|
||||
enum = self._enums[node['type']['qualType']]
|
||||
self._typedefs[node['name']] = ast_nodes.EnumDecl(
|
||||
name=node['name'], declname=enum.declname, values=dict(enum.values))
|
||||
|
||||
@property
|
||||
def enums(self) -> Mapping[str, ast_nodes.EnumDecl]:
|
||||
return self._enums
|
||||
|
||||
@property
|
||||
def typedefs(self) -> Mapping[str, ast_nodes.EnumDecl]:
|
||||
return self._typedefs
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError('Too many command-line arguments.')
|
||||
|
||||
with open(FLAGS.json_path, 'r', encoding='utf-8') as f:
|
||||
root = json.load(f)
|
||||
|
||||
visitor = MjEnumVisitor()
|
||||
|
||||
traverse(root, visitor)
|
||||
|
||||
enums_str = formatter.format_as_python_code(visitor.typedefs)
|
||||
|
||||
print(f'''
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Provides information about MuJoCo API enums.
|
||||
|
||||
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
"""
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import EnumDecl
|
||||
|
||||
ENUMS: Mapping[str, EnumDecl] = {enums_str}
|
||||
'''.strip()) # `print` adds a trailing newline
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
@@ -0,0 +1,159 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Generates functions.py.
|
||||
|
||||
The JSON input can be generated via:
|
||||
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments mujoco.h
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
from google3.third_party.mujoco.introspect import type_parsing
|
||||
from google3.third_party.mujoco.introspect.codegen import formatter
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('header_path', None, 'Path to the original mujoco.h')
|
||||
flags.DEFINE_string(
|
||||
'json_path', None,
|
||||
'Path to the JSON file representing the Clang AST for mujoco.h')
|
||||
|
||||
ClangJsonNode = Mapping[str, Any]
|
||||
|
||||
|
||||
def traverse(node, visitor):
|
||||
visitor.visit(node)
|
||||
children = node.get('inner', [])
|
||||
for child in children:
|
||||
traverse(child, visitor)
|
||||
|
||||
|
||||
class MjFunctionVisitor:
|
||||
"""A Clang AST JSON node visitor for MuJoCo API function declarations."""
|
||||
|
||||
def __init__(self, raw_header):
|
||||
self._raw_header = raw_header
|
||||
self._functions = {}
|
||||
|
||||
def _make_function(self, node: ClangJsonNode) -> ast_nodes.FunctionDecl:
|
||||
"""Makes a FunctionDecl from a Clang AST FunctionDecl node."""
|
||||
name = node['name']
|
||||
return_type = type_parsing.parse_function_return_type(
|
||||
node['type']['qualType'])
|
||||
parameters = []
|
||||
comments = []
|
||||
for child in node['inner']:
|
||||
child_kind = child.get('kind')
|
||||
if child_kind == 'ParmVarDecl':
|
||||
parameters.append(self._make_parameter(child))
|
||||
if child_kind == 'FullComment':
|
||||
comments.append(self._make_comment(child))
|
||||
comment = ' '.join(comments).strip()
|
||||
return ast_nodes.FunctionDecl(
|
||||
name=name, return_type=return_type, parameters=parameters, doc=comment)
|
||||
|
||||
def _make_parameter(
|
||||
self, node: ClangJsonNode) -> ast_nodes.FunctionParameterDecl:
|
||||
"""Makes a ParameterDecl from a Clang AST ParmVarDecl node."""
|
||||
name = node['name']
|
||||
type_name = node['type']['qualType']
|
||||
|
||||
# For a pointer parameters, look up in the original header to see if
|
||||
# n array extent was declared there.
|
||||
if type_name.endswith('*'):
|
||||
decl_begin = node['range']['begin']['offset']
|
||||
decl_end = node['range']['end']['offset'] + node['range']['end']['tokLen']
|
||||
decl = self._raw_header[decl_begin:decl_end]
|
||||
name_begin = node['loc']['offset'] - decl_begin
|
||||
name_end = name_begin + node['loc']['tokLen']
|
||||
type_name = decl[:name_begin] + decl[name_end:]
|
||||
|
||||
return ast_nodes.FunctionParameterDecl(
|
||||
name=name, type=type_parsing.parse_type(type_name))
|
||||
|
||||
def _make_comment(self, node: ClangJsonNode) -> str:
|
||||
"""Makes a comment string from a Clang AST FullComment node."""
|
||||
kind = node.get('kind')
|
||||
if kind == 'TextComment':
|
||||
return node['text'].replace('\N{NO-BREAK SPACE}', ' ')
|
||||
else:
|
||||
strings = []
|
||||
for child in node['inner']:
|
||||
strings.append(self._make_comment(child))
|
||||
return ''.join(strings)
|
||||
|
||||
def visit(self, node: ClangJsonNode) -> None:
|
||||
if (node.get('kind') == 'FunctionDecl' and
|
||||
node.get('name', '').startswith('mj')):
|
||||
func_decl = self._make_function(node)
|
||||
self._functions[func_decl.name] = func_decl
|
||||
|
||||
@property
|
||||
def functions(self) -> Mapping[str, ast_nodes.FunctionDecl]:
|
||||
return self._functions
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError('Too many command-line arguments.')
|
||||
|
||||
with open(FLAGS.json_path, 'r', encoding='utf-8') as f:
|
||||
root = json.load(f)
|
||||
|
||||
with open(FLAGS.header_path, 'r') as f:
|
||||
visitor = MjFunctionVisitor(f.read())
|
||||
|
||||
traverse(root, visitor)
|
||||
|
||||
functions_str = formatter.format_as_python_code(visitor.functions)
|
||||
|
||||
print(f'''
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Provides information about MuJoCo API functions.
|
||||
|
||||
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
"""
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import ArrayType
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import FunctionDecl
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import FunctionParameterDecl
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import PointerType
|
||||
from google3.third_party.mujoco.introspect.ast_nodes import ValueType
|
||||
|
||||
FUNCTIONS: Mapping[str, FunctionDecl] = {functions_str}
|
||||
'''.strip()) # `print` adds a trailing newline
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
Executable
+633
@@ -0,0 +1,633 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Provides information about MuJoCo API enums.
|
||||
|
||||
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
"""
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
from .ast_nodes import EnumDecl
|
||||
|
||||
ENUMS: Mapping[str, EnumDecl] = dict([
|
||||
('mjtDisableBit',
|
||||
EnumDecl(
|
||||
name='mjtDisableBit',
|
||||
declname='enum mjtDisableBit_',
|
||||
values=dict([
|
||||
('mjDSBL_CONSTRAINT', 1),
|
||||
('mjDSBL_EQUALITY', 2),
|
||||
('mjDSBL_FRICTIONLOSS', 4),
|
||||
('mjDSBL_LIMIT', 8),
|
||||
('mjDSBL_CONTACT', 16),
|
||||
('mjDSBL_PASSIVE', 32),
|
||||
('mjDSBL_GRAVITY', 64),
|
||||
('mjDSBL_CLAMPCTRL', 128),
|
||||
('mjDSBL_WARMSTART', 256),
|
||||
('mjDSBL_FILTERPARENT', 512),
|
||||
('mjDSBL_ACTUATION', 1024),
|
||||
('mjDSBL_REFSAFE', 2048),
|
||||
('mjNDISABLE', 12),
|
||||
]),
|
||||
)),
|
||||
('mjtEnableBit',
|
||||
EnumDecl(
|
||||
name='mjtEnableBit',
|
||||
declname='enum mjtEnableBit_',
|
||||
values=dict([
|
||||
('mjENBL_OVERRIDE', 1),
|
||||
('mjENBL_ENERGY', 2),
|
||||
('mjENBL_FWDINV', 4),
|
||||
('mjENBL_SENSORNOISE', 8),
|
||||
('mjNENABLE', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtJoint',
|
||||
EnumDecl(
|
||||
name='mjtJoint',
|
||||
declname='enum mjtJoint_',
|
||||
values=dict([
|
||||
('mjJNT_FREE', 0),
|
||||
('mjJNT_BALL', 1),
|
||||
('mjJNT_SLIDE', 2),
|
||||
('mjJNT_HINGE', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtGeom',
|
||||
EnumDecl(
|
||||
name='mjtGeom',
|
||||
declname='enum mjtGeom_',
|
||||
values=dict([
|
||||
('mjGEOM_PLANE', 0),
|
||||
('mjGEOM_HFIELD', 1),
|
||||
('mjGEOM_SPHERE', 2),
|
||||
('mjGEOM_CAPSULE', 3),
|
||||
('mjGEOM_ELLIPSOID', 4),
|
||||
('mjGEOM_CYLINDER', 5),
|
||||
('mjGEOM_BOX', 6),
|
||||
('mjGEOM_MESH', 7),
|
||||
('mjNGEOMTYPES', 8),
|
||||
('mjGEOM_ARROW', 100),
|
||||
('mjGEOM_ARROW1', 101),
|
||||
('mjGEOM_ARROW2', 102),
|
||||
('mjGEOM_LINE', 103),
|
||||
('mjGEOM_SKIN', 104),
|
||||
('mjGEOM_LABEL', 105),
|
||||
('mjGEOM_NONE', 1001),
|
||||
]),
|
||||
)),
|
||||
('mjtCamLight',
|
||||
EnumDecl(
|
||||
name='mjtCamLight',
|
||||
declname='enum mjtCamLight_',
|
||||
values=dict([
|
||||
('mjCAMLIGHT_FIXED', 0),
|
||||
('mjCAMLIGHT_TRACK', 1),
|
||||
('mjCAMLIGHT_TRACKCOM', 2),
|
||||
('mjCAMLIGHT_TARGETBODY', 3),
|
||||
('mjCAMLIGHT_TARGETBODYCOM', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtTexture',
|
||||
EnumDecl(
|
||||
name='mjtTexture',
|
||||
declname='enum mjtTexture_',
|
||||
values=dict([
|
||||
('mjTEXTURE_2D', 0),
|
||||
('mjTEXTURE_CUBE', 1),
|
||||
('mjTEXTURE_SKYBOX', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtIntegrator',
|
||||
EnumDecl(
|
||||
name='mjtIntegrator',
|
||||
declname='enum mjtIntegrator_',
|
||||
values=dict([
|
||||
('mjINT_EULER', 0),
|
||||
('mjINT_RK4', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtCollision',
|
||||
EnumDecl(
|
||||
name='mjtCollision',
|
||||
declname='enum mjtCollision_',
|
||||
values=dict([
|
||||
('mjCOL_ALL', 0),
|
||||
('mjCOL_PAIR', 1),
|
||||
('mjCOL_DYNAMIC', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtCone',
|
||||
EnumDecl(
|
||||
name='mjtCone',
|
||||
declname='enum mjtCone_',
|
||||
values=dict([
|
||||
('mjCONE_PYRAMIDAL', 0),
|
||||
('mjCONE_ELLIPTIC', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtJacobian',
|
||||
EnumDecl(
|
||||
name='mjtJacobian',
|
||||
declname='enum mjtJacobian_',
|
||||
values=dict([
|
||||
('mjJAC_DENSE', 0),
|
||||
('mjJAC_SPARSE', 1),
|
||||
('mjJAC_AUTO', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtSolver',
|
||||
EnumDecl(
|
||||
name='mjtSolver',
|
||||
declname='enum mjtSolver_',
|
||||
values=dict([
|
||||
('mjSOL_PGS', 0),
|
||||
('mjSOL_CG', 1),
|
||||
('mjSOL_NEWTON', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtEq',
|
||||
EnumDecl(
|
||||
name='mjtEq',
|
||||
declname='enum mjtEq_',
|
||||
values=dict([
|
||||
('mjEQ_CONNECT', 0),
|
||||
('mjEQ_WELD', 1),
|
||||
('mjEQ_JOINT', 2),
|
||||
('mjEQ_TENDON', 3),
|
||||
('mjEQ_DISTANCE', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtWrap',
|
||||
EnumDecl(
|
||||
name='mjtWrap',
|
||||
declname='enum mjtWrap_',
|
||||
values=dict([
|
||||
('mjWRAP_NONE', 0),
|
||||
('mjWRAP_JOINT', 1),
|
||||
('mjWRAP_PULLEY', 2),
|
||||
('mjWRAP_SITE', 3),
|
||||
('mjWRAP_SPHERE', 4),
|
||||
('mjWRAP_CYLINDER', 5),
|
||||
]),
|
||||
)),
|
||||
('mjtTrn',
|
||||
EnumDecl(
|
||||
name='mjtTrn',
|
||||
declname='enum mjtTrn_',
|
||||
values=dict([
|
||||
('mjTRN_JOINT', 0),
|
||||
('mjTRN_JOINTINPARENT', 1),
|
||||
('mjTRN_SLIDERCRANK', 2),
|
||||
('mjTRN_TENDON', 3),
|
||||
('mjTRN_SITE', 4),
|
||||
('mjTRN_UNDEFINED', 1000),
|
||||
]),
|
||||
)),
|
||||
('mjtDyn',
|
||||
EnumDecl(
|
||||
name='mjtDyn',
|
||||
declname='enum mjtDyn_',
|
||||
values=dict([
|
||||
('mjDYN_NONE', 0),
|
||||
('mjDYN_INTEGRATOR', 1),
|
||||
('mjDYN_FILTER', 2),
|
||||
('mjDYN_MUSCLE', 3),
|
||||
('mjDYN_USER', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtGain',
|
||||
EnumDecl(
|
||||
name='mjtGain',
|
||||
declname='enum mjtGain_',
|
||||
values=dict([
|
||||
('mjGAIN_FIXED', 0),
|
||||
('mjGAIN_MUSCLE', 1),
|
||||
('mjGAIN_USER', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtBias',
|
||||
EnumDecl(
|
||||
name='mjtBias',
|
||||
declname='enum mjtBias_',
|
||||
values=dict([
|
||||
('mjBIAS_NONE', 0),
|
||||
('mjBIAS_AFFINE', 1),
|
||||
('mjBIAS_MUSCLE', 2),
|
||||
('mjBIAS_USER', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtObj',
|
||||
EnumDecl(
|
||||
name='mjtObj',
|
||||
declname='enum mjtObj_',
|
||||
values=dict([
|
||||
('mjOBJ_UNKNOWN', 0),
|
||||
('mjOBJ_BODY', 1),
|
||||
('mjOBJ_XBODY', 2),
|
||||
('mjOBJ_JOINT', 3),
|
||||
('mjOBJ_DOF', 4),
|
||||
('mjOBJ_GEOM', 5),
|
||||
('mjOBJ_SITE', 6),
|
||||
('mjOBJ_CAMERA', 7),
|
||||
('mjOBJ_LIGHT', 8),
|
||||
('mjOBJ_MESH', 9),
|
||||
('mjOBJ_SKIN', 10),
|
||||
('mjOBJ_HFIELD', 11),
|
||||
('mjOBJ_TEXTURE', 12),
|
||||
('mjOBJ_MATERIAL', 13),
|
||||
('mjOBJ_PAIR', 14),
|
||||
('mjOBJ_EXCLUDE', 15),
|
||||
('mjOBJ_EQUALITY', 16),
|
||||
('mjOBJ_TENDON', 17),
|
||||
('mjOBJ_ACTUATOR', 18),
|
||||
('mjOBJ_SENSOR', 19),
|
||||
('mjOBJ_NUMERIC', 20),
|
||||
('mjOBJ_TEXT', 21),
|
||||
('mjOBJ_TUPLE', 22),
|
||||
('mjOBJ_KEY', 23),
|
||||
]),
|
||||
)),
|
||||
('mjtConstraint',
|
||||
EnumDecl(
|
||||
name='mjtConstraint',
|
||||
declname='enum mjtConstraint_',
|
||||
values=dict([
|
||||
('mjCNSTR_EQUALITY', 0),
|
||||
('mjCNSTR_FRICTION_DOF', 1),
|
||||
('mjCNSTR_FRICTION_TENDON', 2),
|
||||
('mjCNSTR_LIMIT_JOINT', 3),
|
||||
('mjCNSTR_LIMIT_TENDON', 4),
|
||||
('mjCNSTR_CONTACT_FRICTIONLESS', 5),
|
||||
('mjCNSTR_CONTACT_PYRAMIDAL', 6),
|
||||
('mjCNSTR_CONTACT_ELLIPTIC', 7),
|
||||
]),
|
||||
)),
|
||||
('mjtConstraintState',
|
||||
EnumDecl(
|
||||
name='mjtConstraintState',
|
||||
declname='enum mjtConstraintState_',
|
||||
values=dict([
|
||||
('mjCNSTRSTATE_SATISFIED', 0),
|
||||
('mjCNSTRSTATE_QUADRATIC', 1),
|
||||
('mjCNSTRSTATE_LINEARNEG', 2),
|
||||
('mjCNSTRSTATE_LINEARPOS', 3),
|
||||
('mjCNSTRSTATE_CONE', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtSensor',
|
||||
EnumDecl(
|
||||
name='mjtSensor',
|
||||
declname='enum mjtSensor_',
|
||||
values=dict([
|
||||
('mjSENS_TOUCH', 0),
|
||||
('mjSENS_ACCELEROMETER', 1),
|
||||
('mjSENS_VELOCIMETER', 2),
|
||||
('mjSENS_GYRO', 3),
|
||||
('mjSENS_FORCE', 4),
|
||||
('mjSENS_TORQUE', 5),
|
||||
('mjSENS_MAGNETOMETER', 6),
|
||||
('mjSENS_RANGEFINDER', 7),
|
||||
('mjSENS_JOINTPOS', 8),
|
||||
('mjSENS_JOINTVEL', 9),
|
||||
('mjSENS_TENDONPOS', 10),
|
||||
('mjSENS_TENDONVEL', 11),
|
||||
('mjSENS_ACTUATORPOS', 12),
|
||||
('mjSENS_ACTUATORVEL', 13),
|
||||
('mjSENS_ACTUATORFRC', 14),
|
||||
('mjSENS_BALLQUAT', 15),
|
||||
('mjSENS_BALLANGVEL', 16),
|
||||
('mjSENS_JOINTLIMITPOS', 17),
|
||||
('mjSENS_JOINTLIMITVEL', 18),
|
||||
('mjSENS_JOINTLIMITFRC', 19),
|
||||
('mjSENS_TENDONLIMITPOS', 20),
|
||||
('mjSENS_TENDONLIMITVEL', 21),
|
||||
('mjSENS_TENDONLIMITFRC', 22),
|
||||
('mjSENS_FRAMEPOS', 23),
|
||||
('mjSENS_FRAMEQUAT', 24),
|
||||
('mjSENS_FRAMEXAXIS', 25),
|
||||
('mjSENS_FRAMEYAXIS', 26),
|
||||
('mjSENS_FRAMEZAXIS', 27),
|
||||
('mjSENS_FRAMELINVEL', 28),
|
||||
('mjSENS_FRAMEANGVEL', 29),
|
||||
('mjSENS_FRAMELINACC', 30),
|
||||
('mjSENS_FRAMEANGACC', 31),
|
||||
('mjSENS_SUBTREECOM', 32),
|
||||
('mjSENS_SUBTREELINVEL', 33),
|
||||
('mjSENS_SUBTREEANGMOM', 34),
|
||||
('mjSENS_USER', 35),
|
||||
]),
|
||||
)),
|
||||
('mjtStage',
|
||||
EnumDecl(
|
||||
name='mjtStage',
|
||||
declname='enum mjtStage_',
|
||||
values=dict([
|
||||
('mjSTAGE_NONE', 0),
|
||||
('mjSTAGE_POS', 1),
|
||||
('mjSTAGE_VEL', 2),
|
||||
('mjSTAGE_ACC', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtDataType',
|
||||
EnumDecl(
|
||||
name='mjtDataType',
|
||||
declname='enum mjtDataType_',
|
||||
values=dict([
|
||||
('mjDATATYPE_REAL', 0),
|
||||
('mjDATATYPE_POSITIVE', 1),
|
||||
('mjDATATYPE_AXIS', 2),
|
||||
('mjDATATYPE_QUATERNION', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtLRMode',
|
||||
EnumDecl(
|
||||
name='mjtLRMode',
|
||||
declname='enum mjtLRMode_',
|
||||
values=dict([
|
||||
('mjLRMODE_NONE', 0),
|
||||
('mjLRMODE_MUSCLE', 1),
|
||||
('mjLRMODE_MUSCLEUSER', 2),
|
||||
('mjLRMODE_ALL', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtWarning',
|
||||
EnumDecl(
|
||||
name='mjtWarning',
|
||||
declname='enum mjtWarning_',
|
||||
values=dict([
|
||||
('mjWARN_INERTIA', 0),
|
||||
('mjWARN_CONTACTFULL', 1),
|
||||
('mjWARN_CNSTRFULL', 2),
|
||||
('mjWARN_VGEOMFULL', 3),
|
||||
('mjWARN_BADQPOS', 4),
|
||||
('mjWARN_BADQVEL', 5),
|
||||
('mjWARN_BADQACC', 6),
|
||||
('mjWARN_BADCTRL', 7),
|
||||
('mjNWARNING', 8),
|
||||
]),
|
||||
)),
|
||||
('mjtTimer',
|
||||
EnumDecl(
|
||||
name='mjtTimer',
|
||||
declname='enum mjtTimer_',
|
||||
values=dict([
|
||||
('mjTIMER_STEP', 0),
|
||||
('mjTIMER_FORWARD', 1),
|
||||
('mjTIMER_INVERSE', 2),
|
||||
('mjTIMER_POSITION', 3),
|
||||
('mjTIMER_VELOCITY', 4),
|
||||
('mjTIMER_ACTUATION', 5),
|
||||
('mjTIMER_ACCELERATION', 6),
|
||||
('mjTIMER_CONSTRAINT', 7),
|
||||
('mjTIMER_POS_KINEMATICS', 8),
|
||||
('mjTIMER_POS_INERTIA', 9),
|
||||
('mjTIMER_POS_COLLISION', 10),
|
||||
('mjTIMER_POS_MAKE', 11),
|
||||
('mjTIMER_POS_PROJECT', 12),
|
||||
('mjNTIMER', 13),
|
||||
]),
|
||||
)),
|
||||
('mjtGridPos',
|
||||
EnumDecl(
|
||||
name='mjtGridPos',
|
||||
declname='enum mjtGridPos_',
|
||||
values=dict([
|
||||
('mjGRID_TOPLEFT', 0),
|
||||
('mjGRID_TOPRIGHT', 1),
|
||||
('mjGRID_BOTTOMLEFT', 2),
|
||||
('mjGRID_BOTTOMRIGHT', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtFramebuffer',
|
||||
EnumDecl(
|
||||
name='mjtFramebuffer',
|
||||
declname='enum mjtFramebuffer_',
|
||||
values=dict([
|
||||
('mjFB_WINDOW', 0),
|
||||
('mjFB_OFFSCREEN', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtFontScale',
|
||||
EnumDecl(
|
||||
name='mjtFontScale',
|
||||
declname='enum mjtFontScale_',
|
||||
values=dict([
|
||||
('mjFONTSCALE_50', 50),
|
||||
('mjFONTSCALE_100', 100),
|
||||
('mjFONTSCALE_150', 150),
|
||||
('mjFONTSCALE_200', 200),
|
||||
('mjFONTSCALE_250', 250),
|
||||
('mjFONTSCALE_300', 300),
|
||||
]),
|
||||
)),
|
||||
('mjtFont',
|
||||
EnumDecl(
|
||||
name='mjtFont',
|
||||
declname='enum mjtFont_',
|
||||
values=dict([
|
||||
('mjFONT_NORMAL', 0),
|
||||
('mjFONT_SHADOW', 1),
|
||||
('mjFONT_BIG', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtButton',
|
||||
EnumDecl(
|
||||
name='mjtButton',
|
||||
declname='enum mjtButton_',
|
||||
values=dict([
|
||||
('mjBUTTON_NONE', 0),
|
||||
('mjBUTTON_LEFT', 1),
|
||||
('mjBUTTON_RIGHT', 2),
|
||||
('mjBUTTON_MIDDLE', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtEvent',
|
||||
EnumDecl(
|
||||
name='mjtEvent',
|
||||
declname='enum mjtEvent_',
|
||||
values=dict([
|
||||
('mjEVENT_NONE', 0),
|
||||
('mjEVENT_MOVE', 1),
|
||||
('mjEVENT_PRESS', 2),
|
||||
('mjEVENT_RELEASE', 3),
|
||||
('mjEVENT_SCROLL', 4),
|
||||
('mjEVENT_KEY', 5),
|
||||
('mjEVENT_RESIZE', 6),
|
||||
]),
|
||||
)),
|
||||
('mjtItem',
|
||||
EnumDecl(
|
||||
name='mjtItem',
|
||||
declname='enum mjtItem_',
|
||||
values=dict([
|
||||
('mjITEM_END', -2),
|
||||
('mjITEM_SECTION', -1),
|
||||
('mjITEM_SEPARATOR', 0),
|
||||
('mjITEM_STATIC', 1),
|
||||
('mjITEM_BUTTON', 2),
|
||||
('mjITEM_CHECKINT', 3),
|
||||
('mjITEM_CHECKBYTE', 4),
|
||||
('mjITEM_RADIO', 5),
|
||||
('mjITEM_RADIOLINE', 6),
|
||||
('mjITEM_SELECT', 7),
|
||||
('mjITEM_SLIDERINT', 8),
|
||||
('mjITEM_SLIDERNUM', 9),
|
||||
('mjITEM_EDITINT', 10),
|
||||
('mjITEM_EDITNUM', 11),
|
||||
('mjITEM_EDITTXT', 12),
|
||||
('mjNITEM', 13),
|
||||
]),
|
||||
)),
|
||||
('mjtCatBit',
|
||||
EnumDecl(
|
||||
name='mjtCatBit',
|
||||
declname='enum mjtCatBit_',
|
||||
values=dict([
|
||||
('mjCAT_STATIC', 1),
|
||||
('mjCAT_DYNAMIC', 2),
|
||||
('mjCAT_DECOR', 4),
|
||||
('mjCAT_ALL', 7),
|
||||
]),
|
||||
)),
|
||||
('mjtMouse',
|
||||
EnumDecl(
|
||||
name='mjtMouse',
|
||||
declname='enum mjtMouse_',
|
||||
values=dict([
|
||||
('mjMOUSE_NONE', 0),
|
||||
('mjMOUSE_ROTATE_V', 1),
|
||||
('mjMOUSE_ROTATE_H', 2),
|
||||
('mjMOUSE_MOVE_V', 3),
|
||||
('mjMOUSE_MOVE_H', 4),
|
||||
('mjMOUSE_ZOOM', 5),
|
||||
('mjMOUSE_SELECT', 6),
|
||||
]),
|
||||
)),
|
||||
('mjtPertBit',
|
||||
EnumDecl(
|
||||
name='mjtPertBit',
|
||||
declname='enum mjtPertBit_',
|
||||
values=dict([
|
||||
('mjPERT_TRANSLATE', 1),
|
||||
('mjPERT_ROTATE', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtCamera',
|
||||
EnumDecl(
|
||||
name='mjtCamera',
|
||||
declname='enum mjtCamera_',
|
||||
values=dict([
|
||||
('mjCAMERA_FREE', 0),
|
||||
('mjCAMERA_TRACKING', 1),
|
||||
('mjCAMERA_FIXED', 2),
|
||||
('mjCAMERA_USER', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtLabel',
|
||||
EnumDecl(
|
||||
name='mjtLabel',
|
||||
declname='enum mjtLabel_',
|
||||
values=dict([
|
||||
('mjLABEL_NONE', 0),
|
||||
('mjLABEL_BODY', 1),
|
||||
('mjLABEL_JOINT', 2),
|
||||
('mjLABEL_GEOM', 3),
|
||||
('mjLABEL_SITE', 4),
|
||||
('mjLABEL_CAMERA', 5),
|
||||
('mjLABEL_LIGHT', 6),
|
||||
('mjLABEL_TENDON', 7),
|
||||
('mjLABEL_ACTUATOR', 8),
|
||||
('mjLABEL_CONSTRAINT', 9),
|
||||
('mjLABEL_SKIN', 10),
|
||||
('mjLABEL_SELECTION', 11),
|
||||
('mjLABEL_SELPNT', 12),
|
||||
('mjLABEL_CONTACTFORCE', 13),
|
||||
('mjNLABEL', 14),
|
||||
]),
|
||||
)),
|
||||
('mjtFrame',
|
||||
EnumDecl(
|
||||
name='mjtFrame',
|
||||
declname='enum mjtFrame_',
|
||||
values=dict([
|
||||
('mjFRAME_NONE', 0),
|
||||
('mjFRAME_BODY', 1),
|
||||
('mjFRAME_GEOM', 2),
|
||||
('mjFRAME_SITE', 3),
|
||||
('mjFRAME_CAMERA', 4),
|
||||
('mjFRAME_LIGHT', 5),
|
||||
('mjFRAME_WORLD', 6),
|
||||
('mjNFRAME', 7),
|
||||
]),
|
||||
)),
|
||||
('mjtVisFlag',
|
||||
EnumDecl(
|
||||
name='mjtVisFlag',
|
||||
declname='enum mjtVisFlag_',
|
||||
values=dict([
|
||||
('mjVIS_CONVEXHULL', 0),
|
||||
('mjVIS_TEXTURE', 1),
|
||||
('mjVIS_JOINT', 2),
|
||||
('mjVIS_ACTUATOR', 3),
|
||||
('mjVIS_CAMERA', 4),
|
||||
('mjVIS_LIGHT', 5),
|
||||
('mjVIS_TENDON', 6),
|
||||
('mjVIS_RANGEFINDER', 7),
|
||||
('mjVIS_CONSTRAINT', 8),
|
||||
('mjVIS_INERTIA', 9),
|
||||
('mjVIS_SCLINERTIA', 10),
|
||||
('mjVIS_PERTFORCE', 11),
|
||||
('mjVIS_PERTOBJ', 12),
|
||||
('mjVIS_CONTACTPOINT', 13),
|
||||
('mjVIS_CONTACTFORCE', 14),
|
||||
('mjVIS_CONTACTSPLIT', 15),
|
||||
('mjVIS_TRANSPARENT', 16),
|
||||
('mjVIS_AUTOCONNECT', 17),
|
||||
('mjVIS_COM', 18),
|
||||
('mjVIS_SELECT', 19),
|
||||
('mjVIS_STATIC', 20),
|
||||
('mjVIS_SKIN', 21),
|
||||
('mjNVISFLAG', 22),
|
||||
]),
|
||||
)),
|
||||
('mjtRndFlag',
|
||||
EnumDecl(
|
||||
name='mjtRndFlag',
|
||||
declname='enum mjtRndFlag_',
|
||||
values=dict([
|
||||
('mjRND_SHADOW', 0),
|
||||
('mjRND_WIREFRAME', 1),
|
||||
('mjRND_REFLECTION', 2),
|
||||
('mjRND_ADDITIVE', 3),
|
||||
('mjRND_SKYBOX', 4),
|
||||
('mjRND_FOG', 5),
|
||||
('mjRND_HAZE', 6),
|
||||
('mjRND_SEGMENT', 7),
|
||||
('mjRND_IDCOLOR', 8),
|
||||
('mjNRNDFLAG', 9),
|
||||
]),
|
||||
)),
|
||||
('mjtStereo',
|
||||
EnumDecl(
|
||||
name='mjtStereo',
|
||||
declname='enum mjtStereo_',
|
||||
values=dict([
|
||||
('mjSTEREO_NONE', 0),
|
||||
('mjSTEREO_QUADBUFFERED', 1),
|
||||
('mjSTEREO_SIDEBYSIDE', 2),
|
||||
]),
|
||||
)),
|
||||
])
|
||||
@@ -0,0 +1,66 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Tests for enums.py."""
|
||||
|
||||
from absl.testing import absltest
|
||||
|
||||
from google3.third_party.mujoco.introspect import enums
|
||||
|
||||
|
||||
class EnumsTest(absltest.TestCase):
|
||||
|
||||
# "simple" enum that just starts at zero and increment by one.
|
||||
def test_mjtJoint(self): # pylint: disable=invalid-name
|
||||
enum_decl = enums.ENUMS['mjtJoint']
|
||||
self.assertEqual(enum_decl.name, 'mjtJoint')
|
||||
self.assertEqual(enum_decl.declname, 'enum mjtJoint_')
|
||||
self.assertEqual(
|
||||
tuple(enum_decl.values.items()), (('mjJNT_FREE', 0),
|
||||
('mjJNT_BALL', 1),
|
||||
('mjJNT_SLIDE', 2),
|
||||
('mjJNT_HINGE', 3)))
|
||||
|
||||
# all values explicitly specified
|
||||
def test_mjtEnableBit(self): # pylint: disable=invalid-name
|
||||
enum_decl = enums.ENUMS['mjtEnableBit']
|
||||
self.assertEqual(enum_decl.name, 'mjtEnableBit')
|
||||
self.assertEqual(enum_decl.declname, 'enum mjtEnableBit_')
|
||||
self.assertEqual(
|
||||
tuple(enum_decl.values.items()), (('mjENBL_OVERRIDE', 1<<0),
|
||||
('mjENBL_ENERGY', 1<<1),
|
||||
('mjENBL_FWDINV', 1<<2),
|
||||
('mjENBL_SENSORNOISE', 1<<3),
|
||||
('mjNENABLE', 4)))
|
||||
|
||||
# values mostly increment by one with occasional overrides
|
||||
def test_mjtGeom(self): # pylint: disable=invalid-name
|
||||
enum_decl = enums.ENUMS['mjtGeom']
|
||||
self.assertEqual(enum_decl.name, 'mjtGeom')
|
||||
self.assertEqual(enum_decl.declname, 'enum mjtGeom_')
|
||||
|
||||
self.assertEqual(enum_decl.values['mjGEOM_PLANE'], 0)
|
||||
self.assertEqual(enum_decl.values['mjGEOM_HFIELD'], 1)
|
||||
self.assertEqual(enum_decl.values['mjGEOM_SPHERE'], 2)
|
||||
# Skip a few...
|
||||
|
||||
self.assertEqual(enum_decl.values['mjGEOM_ARROW'], 100)
|
||||
self.assertEqual(enum_decl.values['mjGEOM_ARROW1'], 101)
|
||||
self.assertEqual(enum_decl.values['mjGEOM_ARROW2'], 102)
|
||||
# Skip a few...
|
||||
|
||||
self.assertEqual(enum_decl.values['mjGEOM_NONE'], 1001)
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Tests for functions.py."""
|
||||
|
||||
from absl.testing import absltest
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
from google3.third_party.mujoco.introspect import functions
|
||||
from google3.third_party.mujoco.introspect import type_parsing
|
||||
|
||||
|
||||
class FunctionsTest(absltest.TestCase):
|
||||
|
||||
def test_mj_copyData(self): # pylint: disable=invalid-name
|
||||
func_decl = functions.FUNCTIONS['mj_copyData']
|
||||
self.assertEqual(func_decl.name, 'mj_copyData')
|
||||
self.assertEqual(func_decl.return_type, type_parsing.parse_type('mjData*'))
|
||||
self.assertEqual(
|
||||
func_decl.parameters,
|
||||
(ast_nodes.FunctionParameterDecl(
|
||||
name='dest', type=type_parsing.parse_type('mjData*')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='m', type=type_parsing.parse_type('const mjModel*')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='src', type=type_parsing.parse_type('const mjData*'))))
|
||||
self.assertEqual(
|
||||
func_decl.doc, 'Copy mjData. '
|
||||
'm is only required to contain the size fields from MJMODEL_INTS.')
|
||||
|
||||
def test_mju_transformSpatial(self): # pylint: disable=invalid-name
|
||||
func_decl = functions.FUNCTIONS['mju_transformSpatial']
|
||||
self.assertEqual(func_decl.name, 'mju_transformSpatial')
|
||||
self.assertEqual(func_decl.return_type, type_parsing.parse_type('void'))
|
||||
self.assertEqual(
|
||||
func_decl.parameters,
|
||||
(ast_nodes.FunctionParameterDecl(
|
||||
name='res', type=type_parsing.parse_type('mjtNum[6]')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='vec', type=type_parsing.parse_type('const mjtNum[6]')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='flg_force', type=type_parsing.parse_type('int')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='newpos', type=type_parsing.parse_type('const mjtNum[3]')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='oldpos', type=type_parsing.parse_type('const mjtNum[3]')),
|
||||
ast_nodes.FunctionParameterDecl(
|
||||
name='rotnew2old',
|
||||
type=type_parsing.parse_type('const mjtNum[9]'))))
|
||||
self.assertEqual(
|
||||
func_decl.doc, 'Coordinate transform of 6D motion or force vector in ' +
|
||||
'rotation:translation format. rotnew2old is 3-by-3, ' +
|
||||
'NULL means no rotation; flg_force specifies force or motion type.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
@@ -0,0 +1,148 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Functions for parsing C type declarations."""
|
||||
|
||||
import collections
|
||||
import re
|
||||
from typing import Mapping, MutableSequence, Optional, Sequence, Tuple, Union
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
|
||||
ARRAY_EXTENTS_PATTERN = re.compile(r'(\[[^\]]+\]\s*)+\Z')
|
||||
ARRAY_N_PATTERN = re.compile(r'\[([^\]]+)\]')
|
||||
STARTS_WITH_CONST_PATTERN = re.compile(r'\Aconst(?![A-Za-z0-9_])')
|
||||
ENDS_WITH_CONST_PATTERN = re.compile(r'(?<![A-Za-z0-9_])const\Z')
|
||||
|
||||
|
||||
def _parse_qualifiers(
|
||||
type_name: str,
|
||||
qualifiers: Sequence[str]) -> Tuple[str, Mapping[str, bool]]:
|
||||
"""Separates qualifiers from the rest of the type name."""
|
||||
parts = re.split(r'\s+', type_name)
|
||||
counter = collections.defaultdict(lambda: 0)
|
||||
non_qualifiers = []
|
||||
for part in parts:
|
||||
if part in qualifiers:
|
||||
counter[part] += 1
|
||||
if counter[part] > 1:
|
||||
ValueError('duplicate qualifier: {part!r}')
|
||||
else:
|
||||
non_qualifiers.append(part)
|
||||
is_qualifier = dict()
|
||||
for qualifier in qualifiers:
|
||||
is_qualifier[f'is_{qualifier}'] = bool(counter[qualifier])
|
||||
return ' '.join(non_qualifiers), is_qualifier
|
||||
|
||||
|
||||
def _parse_maybe_array(
|
||||
type_name: str, innermost_type: Optional[Union[ast_nodes.ValueType,
|
||||
ast_nodes.PointerType]]
|
||||
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
|
||||
"""Internal-only helper that parses a type that may be an array type."""
|
||||
array_match = ARRAY_EXTENTS_PATTERN.search(type_name)
|
||||
if array_match:
|
||||
extents = tuple(
|
||||
int(s.strip()) for s in ARRAY_N_PATTERN.findall(array_match.group(0)))
|
||||
inner_type_str = type_name[:array_match.start()]
|
||||
return ast_nodes.ArrayType(
|
||||
inner_type=_parse_maybe_pointer(inner_type_str.strip(), innermost_type),
|
||||
extents=extents)
|
||||
else:
|
||||
return _parse_maybe_pointer(type_name, innermost_type)
|
||||
|
||||
|
||||
def _parse_maybe_pointer(
|
||||
type_name: str, innermost_type: Optional[Union[ast_nodes.ValueType,
|
||||
ast_nodes.PointerType]]
|
||||
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
|
||||
"""Internal-only helper that parses a type that may be a pointer type."""
|
||||
p = type_name.rfind('*')
|
||||
if p != -1:
|
||||
leftover, is_qualifier = _parse_qualifiers(
|
||||
type_name[p + 1:].strip(), ('const', 'volatile', 'restrict'))
|
||||
if leftover:
|
||||
raise ValueError('invalid qualifier for pointer: {leftover!r}')
|
||||
|
||||
inner_type_str = type_name[:p].strip()
|
||||
if inner_type_str:
|
||||
inner_type = _parse_maybe_pointer(inner_type_str, innermost_type)
|
||||
else:
|
||||
assert innermost_type is not None
|
||||
inner_type = innermost_type
|
||||
return ast_nodes.PointerType(inner_type=inner_type, **is_qualifier)
|
||||
else:
|
||||
assert innermost_type is None # value type should be innermost
|
||||
type_name, is_qualifier = _parse_qualifiers(
|
||||
type_name.strip(), ('const', 'volatile'))
|
||||
return ast_nodes.ValueType(name=type_name, **is_qualifier)
|
||||
|
||||
|
||||
def _peel_nested_parens(input_str: str) -> MutableSequence[str]:
|
||||
"""Extracts substrings from a string with nested parentheses.
|
||||
|
||||
The returned sequence starts from the substring enclosed in the innermost
|
||||
parentheses and moves subsequently outwards. The contents of the inner
|
||||
substrings are removed from the outer ones. For example, given the string
|
||||
'lorem ipsum(dolor sit (consectetur adipiscing) amet)sed do eiusmod',
|
||||
this function produces the sequence
|
||||
['consectetur adipiscing', 'dolor sit amet', 'lorem ipsumsed do eiusmod'].
|
||||
|
||||
Args:
|
||||
input_str: An input_str string consisting of zero or more nested
|
||||
parentheses.
|
||||
|
||||
Returns:
|
||||
A sequence of substrings enclosed with in respective parentheses. See the
|
||||
description above for the precise detail of the output.
|
||||
"""
|
||||
start = input_str.find('(')
|
||||
end = input_str.rfind(')')
|
||||
|
||||
if start == -1 and end == -1:
|
||||
return [input_str]
|
||||
else:
|
||||
# Assertions to be re-raised into a meaningful error by the caller.
|
||||
assert start != -1 # '(' should be present if there is a ')'
|
||||
assert end != -1 # ')' should be present if there is a '('
|
||||
assert start < end # '(' should come before ')'
|
||||
out = _peel_nested_parens(input_str[start + 1:end])
|
||||
out.append(input_str[:start] + input_str[end + 1:])
|
||||
return out
|
||||
|
||||
|
||||
def parse_type(
|
||||
type_name: str
|
||||
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
|
||||
"""Parses a string that represents a C type into an AST node."""
|
||||
try:
|
||||
type_str_stack = _peel_nested_parens(type_name.strip())
|
||||
except AssertionError:
|
||||
raise ValueError(f'{type_name!r} contains incorrectly nested parentheses')
|
||||
|
||||
result = None
|
||||
while type_str_stack:
|
||||
try:
|
||||
result = _parse_maybe_array(type_str_stack.pop(), result)
|
||||
except AssertionError:
|
||||
raise ValueError(f'invalid type name {type_name!r}')
|
||||
|
||||
assert result # hint for pytype that `result` isn't None
|
||||
return result
|
||||
|
||||
|
||||
def parse_function_return_type(
|
||||
type_name: str
|
||||
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
|
||||
return parse_type(type_name[:type_name.rfind('(')])
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Tests for type_parsing.py."""
|
||||
|
||||
from absl.testing import absltest
|
||||
|
||||
from google3.third_party.mujoco.introspect import ast_nodes
|
||||
from google3.third_party.mujoco.introspect import type_parsing
|
||||
|
||||
|
||||
class TypeParsingTest(absltest.TestCase):
|
||||
|
||||
def test_parse_complex_type(self):
|
||||
parsed_type = type_parsing.parse_type(
|
||||
'int unsigned volatile long const long'+
|
||||
'(**const(*const restrict*[9])[7])[3][4]')
|
||||
|
||||
expected_type = ast_nodes.ArrayType(
|
||||
extents=[9],
|
||||
inner_type=ast_nodes.PointerType(
|
||||
ast_nodes.PointerType(
|
||||
is_const=True,
|
||||
is_restrict=True,
|
||||
inner_type=ast_nodes.ArrayType(
|
||||
extents=[7],
|
||||
inner_type=ast_nodes.PointerType(
|
||||
is_const=True,
|
||||
inner_type=ast_nodes.PointerType(
|
||||
ast_nodes.ArrayType(
|
||||
extents=(3, 4),
|
||||
inner_type=ast_nodes.ValueType(
|
||||
'int unsigned long long',
|
||||
is_const=True, is_volatile=True)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(parsed_type, expected_type)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
Reference in New Issue
Block a user