Move introspect to python/introspect

PiperOrigin-RevId: 728695024
Change-Id: I96433e1e9ee509704d66bd4c1be1906732e4cc55
This commit is contained in:
Alessio Quaglino
2025-02-19 09:00:26 -08:00
committed by Copybara-Service
parent 6a86247810
commit b0e9d08673
19 changed files with 22 additions and 5 deletions
+2 -2
View File
@@ -36,9 +36,9 @@ cp -r "${package_dir}"/* .
# Generate header files.
old_pythonpath="${PYTHONPATH}"
if [[ "$(uname)" == CYGWIN* || "$(uname)" == MINGW* ]]; then
export PYTHONPATH="${old_pythonpath};${package_dir}/.."
export PYTHONPATH="${old_pythonpath};${package_dir}/mujoco/python/.."
else
export PYTHONPATH="${old_pythonpath}:${package_dir}/.."
export PYTHONPATH="${old_pythonpath}:${package_dir}/mujoco/python/.."
fi
python "${package_dir}"/mujoco/codegen/generate_enum_traits.py > \
mujoco/enum_traits.h
+2
View File
@@ -18,5 +18,7 @@ pyproject_hooks==1.2.0 \
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
tomli==2.1.0; python_version < '3.11' \
--hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d
zipp==3.21.0 \
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
+3 -3
View File
@@ -215,7 +215,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/enum_traits.h)
else()
add_custom_command(
OUTPUT enum_traits.h
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python/mujoco ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_enum_traits.py > enum_traits.h
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_enum_traits.py
)
@@ -233,7 +233,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/function_traits.h)
else()
add_custom_command(
OUTPUT function_traits.h
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python/mujoco ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_function_traits.py > function_traits.h
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_function_traits.py
)
@@ -408,7 +408,7 @@ target_link_libraries(
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/specs.cc.inc)
add_custom_command(
OUTPUT specs.cc.inc
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py > specs.cc.inc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py
)
+14
View File
@@ -0,0 +1,14 @@
# Copyright 2025 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.
# ==============================================================================
+298
View File
@@ -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)
+179
View File
@@ -0,0 +1,179 @@
# 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 . 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]')
def test_struct_decl(self):
struct = ast_nodes.StructDecl(
name='mystruct',
declname='struct mystruct_',
fields=[
ast_nodes.StructFieldDecl(
name='foo',
type=ast_nodes.ValueType('int'),
doc='',
)
],
)
self.assertEqual(struct.decl('var'), 'mystruct var')
def test_anonymous_struct_decl(self):
struct = ast_nodes.AnonymousStructDecl(
fields=[
ast_nodes.StructFieldDecl(
name='foo',
type=ast_nodes.ValueType('int'),
doc='',
),
ast_nodes.StructFieldDecl(
name='bar',
type=ast_nodes.ArrayType(
inner_type=ast_nodes.ValueType('float'), extents=(3,)
),
doc='',
),
],
)
self.assertEqual(str(struct), 'struct {int foo; float bar[3];}')
self.assertEqual(struct.decl('var'), 'struct {int foo; float bar[3];} var')
self.assertEqual(struct.fields[0].decltype, 'int')
self.assertEqual(struct.fields[1].decltype, 'float [3]')
def test_anonymous_union_decl(self):
union = ast_nodes.AnonymousUnionDecl(
fields=[
ast_nodes.StructFieldDecl(
name='foo',
type=ast_nodes.ValueType('int'),
doc='',
),
ast_nodes.StructFieldDecl(
name='bar',
type=ast_nodes.ArrayType(
inner_type=ast_nodes.ValueType('float'), extents=(3,)
),
doc='',
),
],
)
self.assertEqual(str(union), 'union {int foo; float bar[3];}')
self.assertEqual(union.decl('var'), 'union {int foo; float bar[3];} var')
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,128 @@
# 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 -x c mujoco.h
"""
import json
from typing import Any, Mapping, Sequence
from absl import app
from absl import flags
from introspect import ast_nodes
from . import formatter
_JSON_PATH = 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
if 'inner' in child:
value = int(child['inner'][0].get('value', next_idx))
else:
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(_JSON_PATH.value, '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 .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 -x c mujoco.h
"""
import json
from typing import Any, Mapping, Sequence
from absl import app
from absl import flags
from introspect import ast_nodes
from introspect import type_parsing
from . import formatter
_HEADER_PATH = flags.DEFINE_string(
'header_path', None, 'Path to the original mujoco.h')
_JSON_PATH = 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}', '&nbsp;')
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(_JSON_PATH.value, 'r', encoding='utf-8') as f:
root = json.load(f)
with open(_HEADER_PATH.value, '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 .ast_nodes import ArrayType
from .ast_nodes import FunctionDecl
from .ast_nodes import FunctionParameterDecl
from .ast_nodes import PointerType
from .ast_nodes import ValueType
FUNCTIONS: Mapping[str, FunctionDecl] = {functions_str}
'''.strip()) # `print` adds a trailing newline
if __name__ == '__main__':
app.run(main)
@@ -0,0 +1,276 @@
# Copyright 2023 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 structs.py.
The JSON input can be generated via:
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments -x c mujoco.h
"""
import itertools
import json
import os
import re
from typing import Any, Mapping, Sequence, Union
from absl import app
from absl import flags
from introspect import ast_nodes
from introspect import type_parsing
from . import formatter
_JSON_PATH = flags.DEFINE_string(
'json_path', None,
'Path to the JSON file representing the Clang AST for mujoco.h')
ClangJsonNode = Mapping[str, Any]
_ANONYMOUS_KEY_PATTERN = re.compile(r'\d+:\d+(?=\))')
_EXCLUDED = (
'mjpPlugin',
'mjpPlugin_',
'mjpResourceProvider',
'mjpResourceProvider_',
'mjResource',
'mjResource_',
)
_ARRAY_COMMENT_PATTERN = re.compile(r'(.+?)\s\s+\((.+) x (.+)\)\Z')
def traverse(node, visitor):
visitor.visit(node)
children = node.get('inner', [])
for child in children:
traverse(child, visitor)
class _AnonymousTypePlaceholder(ast_nodes.ValueType):
def __init__(self, anonymous_key: str):
self.name = anonymous_key
self.is_const = False
self.is_volatile = False
class MjStructVisitor:
"""A Clang AST JSON node visitor for MuJoCo API struct declarations."""
def __init__(self):
self._structs = {}
self._anonymous = {}
self._typedefs = {}
def _normalize_type(
self, declname: str
) -> Union[
ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
"""Resolves anonymous structs/unions and looks up existing typedefs."""
# Check for anonymous struct/union.
if '(unnamed ' in declname:
m = _ANONYMOUS_KEY_PATTERN.search(declname)
if not m:
raise RuntimeError('cannot parse anonymous key from {m!r}')
return _AnonymousTypePlaceholder(m.group(0))
# Lookup typedef name and use it instead if one exists.
for k, v in self._typedefs.items():
if declname == v.declname:
return type_parsing.parse_type(k)
# No valid normalization, just parse the declname.
return type_parsing.parse_type(declname)
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}', '&nbsp;').strip()
else:
strings = []
for child in node['inner']:
strings.append(self._make_comment(child))
return ''.join(strings).strip()
def _make_field(
self, node: ClangJsonNode
) -> Union[ast_nodes.StructFieldDecl, _AnonymousTypePlaceholder]:
"""Makes a StructFieldDecl object from a Clang AST FieldDecl node."""
doc = ''
for child in node.get('inner', ()):
if child['kind'] == 'FullComment':
doc = self._make_comment(child)
if 'name' in node:
field_type = self._normalize_type(node['type']['qualType'])
m = _ARRAY_COMMENT_PATTERN.match(doc)
if m is None:
array_extent = None
else:
doc = m.group(1)
array_extent_0 = m.group(2)
array_extent_1 = m.group(3)
try:
array_extent_1 = int(array_extent_1)
except ValueError:
pass
if array_extent_1 == 1:
array_extent = (array_extent_0,)
else:
array_extent = (array_extent_0, array_extent_1)
return ast_nodes.StructFieldDecl(
name=node['name'], type=field_type, doc=doc,
array_extent=array_extent)
else:
return _AnonymousTypePlaceholder(self._make_anonymous_key(node))
def _make_struct(
self, node: ClangJsonNode
) -> Union[ast_nodes.AnonymousStructDecl, ast_nodes.StructDecl]:
"""Makes a Decl object from a Clang AST RecordDecl node."""
name = f"{node['tagUsed']} {node['name']}" if 'name' in node else ''
fields = []
for child in node.get('inner', ()):
child_kind = child.get('kind')
if child_kind == 'FieldDecl':
fields.append(self._make_field(child))
if name:
return ast_nodes.StructDecl(name=name, declname=name, fields=fields)
elif node['tagUsed'] == 'union':
return ast_nodes.AnonymousUnionDecl(fields=fields)
else:
return ast_nodes.AnonymousStructDecl(fields=fields)
def _is_mujoco_type(self, node: ClangJsonNode) -> bool:
node_name = node.get('name', '')
included_from = os.path.basename(
node['loc'].get('includedFrom', {}).get('file', '')
)
return node_name not in _EXCLUDED and (
node_name.startswith('mj')
or included_from == 'mujoco.h'
or included_from.startswith('mj')
)
def _make_anonymous_key(self, node: ClangJsonNode) -> str:
line = node['loc']['line']
col = node['loc']['col']
return f'{line}:{col}'
def visit(self, node: ClangJsonNode) -> None:
"""Visits a JSON node."""
if node.get('kind') == 'RecordDecl' and self._is_mujoco_type(node):
struct_decl = self._make_struct(node)
if hasattr(struct_decl, 'name'):
self._structs[struct_decl.name] = struct_decl
else:
anonymous_key = self._make_anonymous_key(node)
if anonymous_key in self._anonymous:
raise RuntimeError(
f'duplicate key for anonymous struct: {anonymous_key}')
self._anonymous[anonymous_key] = struct_decl
elif (node.get('kind') == 'TypedefDecl' and
node['type']['qualType'].startswith('struct mj') and
node['name'] not in _EXCLUDED):
declname = node['type']['qualType']
try:
struct = self._structs[declname]
except KeyError:
self._typedefs[node['name']] = ast_nodes.StructDecl(
name=node['name'], declname=declname, fields=())
else:
self._typedefs[node['name']] = ast_nodes.StructDecl(
name=node['name'], declname=struct.declname, fields=struct.fields)
def resolve_all_anonymous(self) -> None:
"""Replaces anonymous struct placeholders with corresponding decl."""
for struct in itertools.chain(
self._structs.values(), self._typedefs.values()
):
fields = []
for field in struct.fields:
if isinstance(field, _AnonymousTypePlaceholder):
fields.append(self._anonymous[field.name])
elif isinstance(field.type, _AnonymousTypePlaceholder):
fields.append(
ast_nodes.StructFieldDecl(
name=field.name,
type=self._anonymous[field.type.name],
doc=field.doc,
)
)
else:
fields.append(field)
struct.fields = tuple(fields)
@property
def structs(self) -> Mapping[str, ast_nodes.StructDecl]:
return self._structs
@property
def typedefs(self) -> Mapping[str, ast_nodes.StructDecl]:
return self._typedefs
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
with open(_JSON_PATH.value, 'r', encoding='utf-8') as f:
root = json.load(f)
visitor = MjStructVisitor()
traverse(root, visitor)
visitor.resolve_all_anonymous()
structs_str = formatter.format_as_python_code(visitor.typedefs)
print(f'''
# Copyright 2023 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 structs.
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
"""
from typing import Mapping
from .ast_nodes import AnonymousStructDecl
from .ast_nodes import AnonymousUnionDecl
from .ast_nodes import ArrayType
from .ast_nodes import PointerType
from .ast_nodes import StructDecl
from .ast_nodes import StructFieldDecl
from .ast_nodes import ValueType
STRUCTS: Mapping[str, StructDecl] = {structs_str}
'''.strip()) # `print` adds a trailing newline
if __name__ == '__main__':
app.run(main)
+868
View File
@@ -0,0 +1,868 @@
# 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),
('mjDSBL_SENSOR', 4096),
('mjDSBL_MIDPHASE', 8192),
('mjDSBL_EULERDAMP', 16384),
('mjDSBL_AUTORESET', 32768),
('mjNDISABLE', 16),
]),
)),
('mjtEnableBit',
EnumDecl(
name='mjtEnableBit',
declname='enum mjtEnableBit_',
values=dict([
('mjENBL_OVERRIDE', 1),
('mjENBL_ENERGY', 2),
('mjENBL_FWDINV', 4),
('mjENBL_INVDISCRETE', 8),
('mjENBL_MULTICCD', 16),
('mjENBL_ISLAND', 32),
('mjENBL_NATIVECCD', 64),
('mjNENABLE', 7),
]),
)),
('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),
('mjGEOM_SDF', 8),
('mjNGEOMTYPES', 9),
('mjGEOM_ARROW', 100),
('mjGEOM_ARROW1', 101),
('mjGEOM_ARROW2', 102),
('mjGEOM_LINE', 103),
('mjGEOM_LINEBOX', 104),
('mjGEOM_FLEX', 105),
('mjGEOM_SKIN', 106),
('mjGEOM_LABEL', 107),
('mjGEOM_TRIANGLE', 108),
('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),
]),
)),
('mjtTextureRole',
EnumDecl(
name='mjtTextureRole',
declname='enum mjtTextureRole_',
values=dict([
('mjTEXROLE_USER', 0),
('mjTEXROLE_RGB', 1),
('mjTEXROLE_OCCLUSION', 2),
('mjTEXROLE_ROUGHNESS', 3),
('mjTEXROLE_METALLIC', 4),
('mjTEXROLE_NORMAL', 5),
('mjTEXROLE_OPACITY', 6),
('mjTEXROLE_EMISSIVE', 7),
('mjTEXROLE_RGBA', 8),
('mjTEXROLE_ORM', 9),
('mjNTEXROLE', 10),
]),
)),
('mjtIntegrator',
EnumDecl(
name='mjtIntegrator',
declname='enum mjtIntegrator_',
values=dict([
('mjINT_EULER', 0),
('mjINT_RK4', 1),
('mjINT_IMPLICIT', 2),
('mjINT_IMPLICITFAST', 3),
]),
)),
('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_FLEX', 4),
('mjEQ_DISTANCE', 5),
]),
)),
('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_BODY', 5),
('mjTRN_UNDEFINED', 1000),
]),
)),
('mjtDyn',
EnumDecl(
name='mjtDyn',
declname='enum mjtDyn_',
values=dict([
('mjDYN_NONE', 0),
('mjDYN_INTEGRATOR', 1),
('mjDYN_FILTER', 2),
('mjDYN_FILTEREXACT', 3),
('mjDYN_MUSCLE', 4),
('mjDYN_USER', 5),
]),
)),
('mjtGain',
EnumDecl(
name='mjtGain',
declname='enum mjtGain_',
values=dict([
('mjGAIN_FIXED', 0),
('mjGAIN_AFFINE', 1),
('mjGAIN_MUSCLE', 2),
('mjGAIN_USER', 3),
]),
)),
('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_FLEX', 9),
('mjOBJ_MESH', 10),
('mjOBJ_SKIN', 11),
('mjOBJ_HFIELD', 12),
('mjOBJ_TEXTURE', 13),
('mjOBJ_MATERIAL', 14),
('mjOBJ_PAIR', 15),
('mjOBJ_EXCLUDE', 16),
('mjOBJ_EQUALITY', 17),
('mjOBJ_TENDON', 18),
('mjOBJ_ACTUATOR', 19),
('mjOBJ_SENSOR', 20),
('mjOBJ_NUMERIC', 21),
('mjOBJ_TEXT', 22),
('mjOBJ_TUPLE', 23),
('mjOBJ_KEY', 24),
('mjOBJ_PLUGIN', 25),
('mjNOBJECT', 26),
('mjOBJ_FRAME', 100),
]),
)),
('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_CAMPROJECTION', 8),
('mjSENS_JOINTPOS', 9),
('mjSENS_JOINTVEL', 10),
('mjSENS_TENDONPOS', 11),
('mjSENS_TENDONVEL', 12),
('mjSENS_ACTUATORPOS', 13),
('mjSENS_ACTUATORVEL', 14),
('mjSENS_ACTUATORFRC', 15),
('mjSENS_JOINTACTFRC', 16),
('mjSENS_BALLQUAT', 17),
('mjSENS_BALLANGVEL', 18),
('mjSENS_JOINTLIMITPOS', 19),
('mjSENS_JOINTLIMITVEL', 20),
('mjSENS_JOINTLIMITFRC', 21),
('mjSENS_TENDONLIMITPOS', 22),
('mjSENS_TENDONLIMITVEL', 23),
('mjSENS_TENDONLIMITFRC', 24),
('mjSENS_FRAMEPOS', 25),
('mjSENS_FRAMEQUAT', 26),
('mjSENS_FRAMEXAXIS', 27),
('mjSENS_FRAMEYAXIS', 28),
('mjSENS_FRAMEZAXIS', 29),
('mjSENS_FRAMELINVEL', 30),
('mjSENS_FRAMEANGVEL', 31),
('mjSENS_FRAMELINACC', 32),
('mjSENS_FRAMEANGACC', 33),
('mjSENS_SUBTREECOM', 34),
('mjSENS_SUBTREELINVEL', 35),
('mjSENS_SUBTREEANGMOM', 36),
('mjSENS_GEOMDIST', 37),
('mjSENS_GEOMNORMAL', 38),
('mjSENS_GEOMFROMTO', 39),
('mjSENS_E_POTENTIAL', 40),
('mjSENS_E_KINETIC', 41),
('mjSENS_CLOCK', 42),
('mjSENS_PLUGIN', 43),
('mjSENS_USER', 44),
]),
)),
('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),
]),
)),
('mjtSameFrame',
EnumDecl(
name='mjtSameFrame',
declname='enum mjtSameFrame_',
values=dict([
('mjSAMEFRAME_NONE', 0),
('mjSAMEFRAME_BODY', 1),
('mjSAMEFRAME_INERTIA', 2),
('mjSAMEFRAME_BODYROT', 3),
('mjSAMEFRAME_INERTIAROT', 4),
]),
)),
('mjtLRMode',
EnumDecl(
name='mjtLRMode',
declname='enum mjtLRMode_',
values=dict([
('mjLRMODE_NONE', 0),
('mjLRMODE_MUSCLE', 1),
('mjLRMODE_MUSCLEUSER', 2),
('mjLRMODE_ALL', 3),
]),
)),
('mjtFlexSelf',
EnumDecl(
name='mjtFlexSelf',
declname='enum mjtFlexSelf_',
values=dict([
('mjFLEXSELF_NONE', 0),
('mjFLEXSELF_NARROW', 1),
('mjFLEXSELF_BVH', 2),
('mjFLEXSELF_SAP', 3),
('mjFLEXSELF_AUTO', 4),
]),
)),
('mjtTaskStatus',
EnumDecl(
name='mjtTaskStatus',
declname='enum mjtTaskStatus_',
values=dict([
('mjTASK_NEW', 0),
('mjTASK_QUEUED', 1),
('mjTASK_COMPLETED', 2),
]),
)),
('mjtState',
EnumDecl(
name='mjtState',
declname='enum mjtState_',
values=dict([
('mjSTATE_TIME', 1),
('mjSTATE_QPOS', 2),
('mjSTATE_QVEL', 4),
('mjSTATE_ACT', 8),
('mjSTATE_WARMSTART', 16),
('mjSTATE_CTRL', 32),
('mjSTATE_QFRC_APPLIED', 64),
('mjSTATE_XFRC_APPLIED', 128),
('mjSTATE_EQ_ACTIVE', 256),
('mjSTATE_MOCAP_POS', 512),
('mjSTATE_MOCAP_QUAT', 1024),
('mjSTATE_USERDATA', 2048),
('mjSTATE_PLUGIN', 4096),
('mjNSTATE', 13),
('mjSTATE_PHYSICS', 14),
('mjSTATE_FULLPHYSICS', 4111),
('mjSTATE_USER', 4064),
('mjSTATE_INTEGRATION', 8191),
]),
)),
('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_CONSTRAINT', 6),
('mjTIMER_ADVANCE', 7),
('mjTIMER_POS_KINEMATICS', 8),
('mjTIMER_POS_INERTIA', 9),
('mjTIMER_POS_COLLISION', 10),
('mjTIMER_POS_MAKE', 11),
('mjTIMER_POS_PROJECT', 12),
('mjTIMER_COL_BROAD', 13),
('mjTIMER_COL_NARROW', 14),
('mjNTIMER', 15),
]),
)),
('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_FLEX', 10),
('mjLABEL_SKIN', 11),
('mjLABEL_SELECTION', 12),
('mjLABEL_SELPNT', 13),
('mjLABEL_CONTACTPOINT', 14),
('mjLABEL_CONTACTFORCE', 15),
('mjLABEL_ISLAND', 16),
('mjNLABEL', 17),
]),
)),
('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_CONTACT', 6),
('mjFRAME_WORLD', 7),
('mjNFRAME', 8),
]),
)),
('mjtVisFlag',
EnumDecl(
name='mjtVisFlag',
declname='enum mjtVisFlag_',
values=dict([
('mjVIS_CONVEXHULL', 0),
('mjVIS_TEXTURE', 1),
('mjVIS_JOINT', 2),
('mjVIS_CAMERA', 3),
('mjVIS_ACTUATOR', 4),
('mjVIS_ACTIVATION', 5),
('mjVIS_LIGHT', 6),
('mjVIS_TENDON', 7),
('mjVIS_RANGEFINDER', 8),
('mjVIS_CONSTRAINT', 9),
('mjVIS_INERTIA', 10),
('mjVIS_SCLINERTIA', 11),
('mjVIS_PERTFORCE', 12),
('mjVIS_PERTOBJ', 13),
('mjVIS_CONTACTPOINT', 14),
('mjVIS_ISLAND', 15),
('mjVIS_CONTACTFORCE', 16),
('mjVIS_CONTACTSPLIT', 17),
('mjVIS_TRANSPARENT', 18),
('mjVIS_AUTOCONNECT', 19),
('mjVIS_COM', 20),
('mjVIS_SELECT', 21),
('mjVIS_STATIC', 22),
('mjVIS_SKIN', 23),
('mjVIS_FLEXVERT', 24),
('mjVIS_FLEXEDGE', 25),
('mjVIS_FLEXFACE', 26),
('mjVIS_FLEXSKIN', 27),
('mjVIS_BODYBVH', 28),
('mjVIS_FLEXBVH', 29),
('mjVIS_MESHBVH', 30),
('mjVIS_SDFITER', 31),
('mjNVISFLAG', 32),
]),
)),
('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),
('mjRND_CULL_FACE', 9),
('mjNRNDFLAG', 10),
]),
)),
('mjtStereo',
EnumDecl(
name='mjtStereo',
declname='enum mjtStereo_',
values=dict([
('mjSTEREO_NONE', 0),
('mjSTEREO_QUADBUFFERED', 1),
('mjSTEREO_SIDEBYSIDE', 2),
]),
)),
('mjtPluginCapabilityBit',
EnumDecl(
name='mjtPluginCapabilityBit',
declname='enum mjtPluginCapabilityBit_',
values=dict([
('mjPLUGIN_ACTUATOR', 1),
('mjPLUGIN_SENSOR', 2),
('mjPLUGIN_PASSIVE', 4),
('mjPLUGIN_SDF', 8),
]),
)),
('mjtGridPos',
EnumDecl(
name='mjtGridPos',
declname='enum mjtGridPos_',
values=dict([
('mjGRID_TOPLEFT', 0),
('mjGRID_TOPRIGHT', 1),
('mjGRID_BOTTOMLEFT', 2),
('mjGRID_BOTTOMRIGHT', 3),
('mjGRID_TOP', 4),
('mjGRID_BOTTOM', 5),
('mjGRID_LEFT', 6),
('mjGRID_RIGHT', 7),
]),
)),
('mjtFramebuffer',
EnumDecl(
name='mjtFramebuffer',
declname='enum mjtFramebuffer_',
values=dict([
('mjFB_WINDOW', 0),
('mjFB_OFFSCREEN', 1),
]),
)),
('mjtDepthMap',
EnumDecl(
name='mjtDepthMap',
declname='enum mjtDepthMap_',
values=dict([
('mjDEPTH_ZERONEAR', 0),
('mjDEPTH_ZEROFAR', 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),
]),
)),
('mjtGeomInertia',
EnumDecl(
name='mjtGeomInertia',
declname='enum mjtGeomInertia_',
values=dict([
('mjINERTIA_VOLUME', 0),
('mjINERTIA_SHELL', 1),
]),
)),
('mjtMeshInertia',
EnumDecl(
name='mjtMeshInertia',
declname='enum mjtMeshInertia_',
values=dict([
('mjMESH_INERTIA_CONVEX', 0),
('mjMESH_INERTIA_EXACT', 1),
('mjMESH_INERTIA_LEGACY', 2),
('mjMESH_INERTIA_SHELL', 3),
]),
)),
('mjtBuiltin',
EnumDecl(
name='mjtBuiltin',
declname='enum mjtBuiltin_',
values=dict([
('mjBUILTIN_NONE', 0),
('mjBUILTIN_GRADIENT', 1),
('mjBUILTIN_CHECKER', 2),
('mjBUILTIN_FLAT', 3),
]),
)),
('mjtMark',
EnumDecl(
name='mjtMark',
declname='enum mjtMark_',
values=dict([
('mjMARK_NONE', 0),
('mjMARK_EDGE', 1),
('mjMARK_CROSS', 2),
('mjMARK_RANDOM', 3),
]),
)),
('mjtLimited',
EnumDecl(
name='mjtLimited',
declname='enum mjtLimited_',
values=dict([
('mjLIMITED_FALSE', 0),
('mjLIMITED_TRUE', 1),
('mjLIMITED_AUTO', 2),
]),
)),
('mjtAlignFree',
EnumDecl(
name='mjtAlignFree',
declname='enum mjtAlignFree_',
values=dict([
('mjALIGNFREE_FALSE', 0),
('mjALIGNFREE_TRUE', 1),
('mjALIGNFREE_AUTO', 2),
]),
)),
('mjtInertiaFromGeom',
EnumDecl(
name='mjtInertiaFromGeom',
declname='enum mjtInertiaFromGeom_',
values=dict([
('mjINERTIAFROMGEOM_FALSE', 0),
('mjINERTIAFROMGEOM_TRUE', 1),
('mjINERTIAFROMGEOM_AUTO', 2),
]),
)),
('mjtOrientation',
EnumDecl(
name='mjtOrientation',
declname='enum mjtOrientation_',
values=dict([
('mjORIENTATION_QUAT', 0),
('mjORIENTATION_AXISANGLE', 1),
('mjORIENTATION_XYAXES', 2),
('mjORIENTATION_ZAXIS', 3),
('mjORIENTATION_EULER', 4),
]),
)),
('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),
('mjEVENT_REDRAW', 7),
('mjEVENT_FILESDROP', 8),
]),
)),
('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_EDITFLOAT', 12),
('mjITEM_EDITTXT', 13),
('mjNITEM', 14),
]),
)),
('mjtSection',
EnumDecl(
name='mjtSection',
declname='enum mjtSection_',
values=dict([
('mjSECT_CLOSED', 0),
('mjSECT_OPEN', 1),
('mjSECT_FIXED', 2),
]),
)),
])
+70
View File
@@ -0,0 +1,70 @@
# 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 . 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_INVDISCRETE', 1<<3),
('mjENBL_MULTICCD', 1<<4),
('mjENBL_ISLAND', 1<<5),
('mjENBL_NATIVECCD', 1<<6),
('mjNENABLE', 7)))
# 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)
self.assertEqual(enum_decl.values['mjGEOM_TRIANGLE'], 108)
# 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 . import ast_nodes
from . import functions
from . 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()
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
# Copyright 2023 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 structs.py."""
import re
from absl.testing import absltest
from . import ast_nodes
from . import structs
from . import type_parsing
class StructsTest(absltest.TestCase):
def test_mjData(self): # pylint: disable=invalid-name
struct_decl = structs.STRUCTS['mjData']
self.assertEqual(struct_decl.name, 'mjData')
self.assertEqual(struct_decl.declname, 'struct mjData_')
field_names = set()
for field in struct_decl.fields:
self.assertNotIn(field.name, field_names)
field_names.add(field.name)
if field.name == 'warning':
self.assertEqual(field.type,
type_parsing.parse_type('mjWarningStat[8]'))
self.assertEqual(field.doc, 'warning statistics')
elif field.name == 'qpos':
self.assertEqual(field.type, type_parsing.parse_type('mjtNum*'))
self.assertEqual(field.doc, 'position')
self.assertEqual(field.array_extent, ('nq',))
self.assertIn('warning', field_names)
self.assertIn('qpos', field_names)
def test_mjVisual(self): # pylint: disable=invalid-name
struct_decl = structs.STRUCTS['mjVisual']
self.assertEqual(struct_decl.name, 'mjVisual')
self.assertEqual(struct_decl.declname, 'struct mjVisual_')
outer_fields = set()
for outer_field in struct_decl.fields:
self.assertNotIn(outer_field.name, outer_fields)
outer_fields.add(outer_field.name)
self.assertIsInstance(outer_field.type, ast_nodes.AnonymousStructDecl)
inner_fields = set()
if outer_field.name == 'global':
for inner_field in outer_field.type.fields:
self.assertNotIn(inner_field.name, inner_fields)
inner_fields.add(inner_field.name)
if inner_field.name == 'ipd':
self.assertEqual(inner_field.type, type_parsing.parse_type('float'))
self.assertEqual(
inner_field.doc, 'inter-pupilary distance for free camera'
)
elif inner_field.name == 'offwidth':
self.assertEqual(inner_field.type, type_parsing.parse_type('int'))
self.assertEqual(inner_field.doc, 'width of offscreen buffer')
self.assertIn('ipd', inner_fields)
self.assertIn('offwidth', inner_fields)
elif outer_field.name == 'headlight':
for inner_field in outer_field.type.fields:
self.assertNotIn(inner_field.name, inner_fields)
inner_fields.add(inner_field.name)
if inner_field.name in {'ambient', 'diffuse', 'specular'}:
self.assertEqual(inner_field.type,
type_parsing.parse_type('float[3]'))
self.assertEqual(inner_field.doc,
f'{inner_field.name} rgb (alpha=1)')
elif inner_field.name == 'active':
self.assertEqual(inner_field.type, type_parsing.parse_type('int'))
self.assertEqual(inner_field.doc, 'is headlight active')
self.assertIn('ambient', inner_fields)
self.assertIn('diffuse', inner_fields)
self.assertIn('specular', inner_fields)
self.assertIn('active', inner_fields)
self.assertIn('global', outer_fields)
self.assertIn('headlight', outer_fields)
def test_mjuiItem(self): # pylint: disable=invalid-name
struct_decl = structs.STRUCTS['mjuiItem']
self.assertEqual(struct_decl.name, 'mjuiItem')
self.assertEqual(struct_decl.declname, 'struct mjuiItem_')
found_anonymous_union = False
outer_fields = set()
for outer_field in struct_decl.fields:
if isinstance(outer_field, ast_nodes.AnonymousUnionDecl):
self.assertFalse(found_anonymous_union)
found_anonymous_union = True
inner_fields = set()
for inner_field in outer_field.fields:
self.assertNotIn(inner_field.name, inner_fields)
inner_fields.add(inner_field.name)
if inner_field.name == 'single':
self.assertEqual(inner_field.type,
type_parsing.parse_type('struct mjuiItemSingle_'))
self.assertEqual(inner_field.doc, 'check and button')
elif inner_field.name == 'multi':
self.assertEqual(inner_field.type,
type_parsing.parse_type('struct mjuiItemMulti_'))
self.assertEqual(inner_field.doc, 'static, radio and select')
self.assertIn('single', inner_fields)
self.assertIn('multi', inner_fields)
else:
self.assertNotIn(outer_field.name, outer_fields)
outer_fields.add(outer_field.name)
if outer_field.name == 'pdata':
self.assertEqual(outer_field.type, type_parsing.parse_type('void*'))
self.assertEqual(outer_field.doc, 'data pointer (type-specific)')
self.assertTrue(found_anonymous_union)
self.assertIn('pdata', outer_fields)
if __name__ == '__main__':
absltest.main()
+154
View File
@@ -0,0 +1,154 @@
# 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 . 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:
raise 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."""
if type_name == 'void *(*)(void *)':
return ast_nodes.ValueType(name=type_name)
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.
"""
if input_str == 'void *(*)(void *)':
return ['void *(*)(void *)']
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 as e:
raise ValueError(f'{type_name!r} contains incorrectly nested '
f'parentheses') from e
result = None
while type_str_stack:
try:
result = _parse_maybe_array(type_str_stack.pop(), result)
except AssertionError as e:
raise ValueError(f'invalid type name {type_name!r}') from e
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.find('(')])
@@ -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 . import ast_nodes
from . 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()